收紧图集恢复与模块投影一致性
拆分 Windows 祖先固定与事务叶子句柄权限,避免重复捕获共享冲突 为 Unix 恢复前态增加非阻塞稳定双读、元数据与叶子身份复核 保持缺失合同父目录无副作用并补齐 CAS 与残留清理回归 按 Oxc symbol span 投影 import alias、namespace、shorthand 与 bridge 同步图集事务和 Tetris ESM 权威技术约束
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+308
-124
File diff suppressed because it is too large
Load Diff
+161
-1
@@ -2383,6 +2383,48 @@ import './gameplay/tetris.mjs';"#,
|
||||
"explicitly imported exported gameplay bindings may form one semantic chain",
|
||||
);
|
||||
|
||||
fs::write(
|
||||
root.join("game/scope-b.mjs"),
|
||||
format!(
|
||||
"import {{ board, current, spin as turnPiece, clearLines as clearRows }} from './scope-a.mjs';\nconst importerMetadata={{ rotatePiece: 'property-only' }};\nconst aliasView={{ turnPiece }};\nfunction shadow(turnPiece) {{ return turnPiece(); }}\nturnPiece();\n{}",
|
||||
gameplay[split_at..]
|
||||
.replace("rotatePiece", "turnPiece")
|
||||
.replace("clearLines();", "clearRows();")
|
||||
),
|
||||
)
|
||||
.expect("write aliased consumer with property and parameter shadows");
|
||||
let aliased_modules = read_external_gameplay_javascript_at(root, &bound_module_html)
|
||||
.expect("read symbol-aware aliased module projection");
|
||||
let aliased_unit = aliased_modules
|
||||
.module_units()
|
||||
.iter()
|
||||
.find(|unit| {
|
||||
unit.contains("function rotatepiece()")
|
||||
&& unit.contains("const importermetadata={ rotatepiece: 'property-only' }")
|
||||
})
|
||||
.expect("aliased importer must be joined with its renamed origin projection");
|
||||
assert!(
|
||||
aliased_unit.contains("function shadow(turnpiece) { return turnpiece(); }"),
|
||||
"a local parameter shadow in the importer must not be confused with the import symbol",
|
||||
);
|
||||
assert!(
|
||||
aliased_unit.contains("const importermetadata={ rotatepiece: 'property-only' }"),
|
||||
"an importer object property must not be rewritten as an imported binding",
|
||||
);
|
||||
assert!(
|
||||
aliased_unit.contains("const aliasview={ turnpiece: rotatepiece };"),
|
||||
"an imported shorthand value must be projected without changing its object key",
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap_with_external_javascript(
|
||||
task,
|
||||
bound_module_html.as_bytes(),
|
||||
&aliased_modules,
|
||||
),
|
||||
None,
|
||||
"an import alias must project only the dependency's root binding references",
|
||||
);
|
||||
|
||||
fs::write(
|
||||
root.join("game/shadowed-import.mjs"),
|
||||
format!(
|
||||
@@ -2461,7 +2503,7 @@ import './gameplay/tetris.mjs';"#,
|
||||
.replace("let current=", "export let current=")
|
||||
.replace("function rotatePiece()", "export function rotatePiece()")
|
||||
.replace("function clearLines()", "export function clearLines()");
|
||||
fs::write(root.join("game/scope-a.mjs"), namespace_scope_a)
|
||||
fs::write(root.join("game/scope-a.mjs"), &namespace_scope_a)
|
||||
.expect("write namespace-exported gameplay bindings");
|
||||
let namespace_consumer = gameplay[split_at..]
|
||||
.replace("board", "gameplay.board")
|
||||
@@ -2510,6 +2552,76 @@ import './gameplay/tetris.mjs';"#,
|
||||
"a namespace re-export must preserve statically referenced member bindings",
|
||||
);
|
||||
|
||||
fs::write(
|
||||
root.join("game/import-export-bridge.mjs"),
|
||||
"import { board as importedBoard, current as importedCurrent, rotatePiece as importedRotate, clearLines as importedClear } from './scope-a.mjs'; export { importedBoard as board, importedCurrent as current, importedRotate as rotatePiece, importedClear as clearLines };",
|
||||
)
|
||||
.expect("write import-then-export bridge");
|
||||
fs::write(
|
||||
root.join("game/scope-b.mjs"),
|
||||
format!(
|
||||
"import {{ board, current, rotatePiece, clearLines }} from './import-export-bridge.mjs';\nrotatePiece();\n{}",
|
||||
&gameplay[split_at..]
|
||||
),
|
||||
)
|
||||
.expect("write import-then-export bridge consumer");
|
||||
let bridge_modules = read_external_gameplay_javascript_at(root, &bound_module_html)
|
||||
.expect("read modules joined through an import-then-export bridge");
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap_with_external_javascript(
|
||||
task,
|
||||
bound_module_html.as_bytes(),
|
||||
&bridge_modules,
|
||||
),
|
||||
None,
|
||||
"a local import-then-export bridge must resolve to the dependency's binding origin",
|
||||
);
|
||||
|
||||
fs::write(root.join("game/scope-a.mjs"), namespace_scope_a)
|
||||
.expect("restore namespace-exported gameplay bindings");
|
||||
fs::write(
|
||||
root.join("game/scope-b.mjs"),
|
||||
format!(
|
||||
"import * as gameplay from './scope-a.mjs';\ngameplay.rotatePiece();\nconst namespaceView = {{ rotatePiece: gameplay.rotatePiece }};\nconst namespaceMetadata = {{ rotatePiece: 'property-only' }};\nfunction shadow(gameplay) {{ gameplay.rotatePiece(); }}\n{namespace_consumer}"
|
||||
),
|
||||
)
|
||||
.expect("write shadowed namespace consumer");
|
||||
let shadowed_namespace_modules = read_external_gameplay_javascript_at(root, &bound_module_html)
|
||||
.expect("read namespace consumer with a shadowed parameter");
|
||||
let projected_namespace_unit = shadowed_namespace_modules
|
||||
.module_units()
|
||||
.iter()
|
||||
.find(|unit| {
|
||||
unit.contains("function shadow(gameplay)") && unit.contains("function rotatepiece()")
|
||||
})
|
||||
.expect("namespace consumer must be joined with the referenced export projection");
|
||||
assert!(
|
||||
projected_namespace_unit.contains("function shadow(gameplay) { gameplay.rotatepiece(); }"),
|
||||
"a shadowed namespace parameter must retain its member access in the projected unit",
|
||||
);
|
||||
assert!(
|
||||
!projected_namespace_unit.contains("function shadow(gameplay) { rotatepiece(); }"),
|
||||
"projection must not rewrite a member access resolved to a shadowing parameter",
|
||||
);
|
||||
assert!(
|
||||
projected_namespace_unit
|
||||
.contains("const namespaceview = { rotatepiece: rotatepiece };"),
|
||||
"namespace projection must replace the complete imported member value while preserving its object key",
|
||||
);
|
||||
assert!(
|
||||
projected_namespace_unit
|
||||
.contains("const namespacemetadata = { rotatepiece: 'property-only' };"),
|
||||
"an unrelated importer object property must remain unchanged",
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap_with_external_javascript(
|
||||
task,
|
||||
bound_module_html.as_bytes(),
|
||||
&shadowed_namespace_modules,
|
||||
),
|
||||
None,
|
||||
);
|
||||
|
||||
fs::write(
|
||||
root.join("game/invalid-link.mjs"),
|
||||
"import { missingGameplay } from './scope-a.mjs'; missingGameplay();",
|
||||
@@ -2612,6 +2724,54 @@ import './gameplay/tetris.mjs';"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn javascript_module_projection_uses_symbols_for_dependencies_and_renames() {
|
||||
let projection = javascript_module_binding_projection(
|
||||
"const board = Array.from({ length: 20 }, () => Array(10).fill(0));\nfunction clearLines() { board.splice(0, 1); }\nexport function rotatePiece() { const metadata = { board: 'property-only' }; function shadow(clearLines) { return clearLines(); } return metadata; }",
|
||||
&BTreeSet::from(["rotatepiece".to_string()]),
|
||||
);
|
||||
assert!(projection.contains("function rotatePiece()"));
|
||||
assert!(
|
||||
!projection.contains("const board ="),
|
||||
"an object property name must not pull an unrelated top-level binding into projection",
|
||||
);
|
||||
assert!(
|
||||
!projection.contains("function clearLines()"),
|
||||
"a shadowed parameter reference must not pull the same-named root binding into projection",
|
||||
);
|
||||
|
||||
let mut renamed = "const rotatePieceMetadata = { rotatePiece: 'property-only' };\nfunction shadow(rotatePiece) { return rotatePiece(); }\nexport function rotatePiece() { const explicit = { rotatePiece: 1 }; const shorthand = { rotatePiece }; shadow(() => 0); return [rotatePieceMetadata.rotatePiece, explicit.rotatePiece, shorthand]; }".to_string();
|
||||
assert!(rename_javascript_root_binding(
|
||||
&mut renamed,
|
||||
"rotatePiece",
|
||||
"turnPiece",
|
||||
));
|
||||
assert!(renamed.contains("export function turnPiece()"));
|
||||
assert!(renamed.contains("function shadow(rotatePiece) { return rotatePiece(); }"));
|
||||
assert!(renamed.contains("{ rotatePiece: 1 }"));
|
||||
assert!(renamed.contains("rotatePieceMetadata.rotatePiece"));
|
||||
assert!(
|
||||
renamed.contains("const shorthand = { rotatePiece: turnPiece };"),
|
||||
"renaming a shorthand binding reference must preserve its original object key",
|
||||
);
|
||||
|
||||
let mut disjoint = "alpha beta gamma".to_string();
|
||||
let mut disjoint_replacements = vec![(0..5, "a".to_string()), (11..16, "g".to_string())];
|
||||
assert!(apply_javascript_span_replacements(
|
||||
&mut disjoint,
|
||||
&mut disjoint_replacements,
|
||||
));
|
||||
assert_eq!(disjoint, "a beta g");
|
||||
|
||||
let mut overlapping = "alpha beta".to_string();
|
||||
let mut overlapping_replacements =
|
||||
vec![(0..5, "a".to_string()), (0..10, "conflict".to_string())];
|
||||
assert!(
|
||||
!apply_javascript_span_replacements(&mut overlapping, &mut overlapping_replacements),
|
||||
"overlapping AST spans must fail closed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_chat_pure_continue_does_not_inherit_across_sessions() {
|
||||
let (_temporary, root, original_state, original_contract) =
|
||||
|
||||
@@ -5976,7 +5976,7 @@
|
||||
|
||||
## 2026-08-04 图集事务与 Tetris 完成门使用句柄和 AST 收口
|
||||
|
||||
- 图集事务:九文件旧合同在写 `prepared` 前必须全部持有可信源句柄并整体复读;Unix 事务控制文件统一通过锚定目录句柄的 `openat / unlinkat + O_NOFOLLOW` 操作,Windows 目录句柄拒绝 delete sharing 并用文件身份复核。恢复目标从 canonical 项目根逐组件拒绝 symlink / reparse point,CAS 安装后的任何清理错误都按实际 canonical 状态把当前项纳入逆序回滚,不能留下新旧混合合同。
|
||||
- JavaScript / ESM:Tetris 静态连续性检查以 Oxc parser、semantic 与 AST visitor 为权威。无效语法、ASI、template interpolation、正则 / 注释、表达式体箭头、参数和词法遮蔽、export alias、re-export 与缺失导出链接不再由字符串扫描猜测;HTML `type` 存在时优先于 legacy `language`。源码投影仍只是静态语义门,最终完成继续要求绑定当前 revision 的真实 Chromium 固定试玩回执。
|
||||
- 图集事务:九文件旧合同在写 `prepared` 前必须全部持有可信源句柄并整体复读;Unix 事务控制文件统一通过锚定目录句柄的 `openat / unlinkat + O_NOFOLLOW + O_NONBLOCK` 操作,FIFO 等非普通文件必须在读取前失败关闭,恢复前态也必须在同一叶子句柄上稳定双读并复核前后元数据与当前 inode。Windows 祖先 pin 只请求读访问并拒绝 delete sharing,可重复持有;只有事务叶子句柄请求删除访问。事务捕获与恢复 CAS 从 canonical 项目根句柄逐组件打开或创建父目录,staging、no-replace link/move 与 unlink 均相对固定父目录句柄执行;清理事务证据前再次复核整组安装结果。安装后的任何清理错误都按实际 canonical 状态把当前项纳入逆序回滚,不能留下新旧混合合同。
|
||||
- JavaScript / ESM:Tetris 静态连续性检查以 Oxc parser、semantic 与 AST visitor 为权威。无效语法、ASI、template interpolation、正则 / 注释、表达式体箭头、参数和词法遮蔽、export alias、import 后再 export 的 bridge、re-export 与缺失导出链接不再由字符串扫描猜测;跨模块 alias 保留 origin 根绑定名称,只按解析到 import symbol 的 reference span 改写 importer,object shorthand 展开后保留原键;namespace 只改写绑定到 import symbol 的完整 member span,同文本属性和局部遮蔽均不得连带改写。HTML `type` 存在时优先于 legacy `language`。源码投影仍只是静态语义门,最终完成继续要求绑定当前 revision 的真实 Chromium 固定试玩回执。
|
||||
- 浏览器因果:状态证据仍只冻结 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` 失败误报为源码失败。
|
||||
|
||||
@@ -827,9 +827,9 @@ game-project/
|
||||
- 2026-08-03 失败续跑收口:同一 `project-supervisor` Session、同一持久 source 的最近可信根 run 已失败、取消或预算耗尽,且新输入只是严格受限的继续意图(例如“继续”“接着做”“继续完成”“continue”“go on”)时,宿主仍创建新的 root run 身份,但必须把上一根 run 的原始任务作为继承目标和完成合同基线;首次和连续 successor 的 effective task、合同 SHA、Runtime hydration 与 scheduler 必须一致。不得把继续短语本身当游戏主题,也不得按真正新需求重置 seed manifest。跨 Session、跨 GUI / CLI / game-chat source、上一根 run 已正常完成、输入包含新的具体玩法要求或无法唯一识别前序根 run 时都不继承,继续按新任务执行。继承只复用目标与已有产物基线,不复用旧 Provider request、pending action 或副作用身份。
|
||||
- game-chat 快车道只能在 `game/index.html` 缺失或仍是初始化占位,且当前 child run 尚未写入正式入口时使用首次 fallback `file.write`。项目已存在非占位入口时,后续 `code-prototype` 必须先保留并读取既有玩法,做真实局部修改并取得本人 `mutationRevision`,之后才能运行 `game.static_smoke` 与交付;禁止为了满足首版时限重新生成整份默认小游戏,也禁止连续只读 smoke。占位 fallback 仅允许俄罗斯方块和明确收集类等已有真实语义模板,未知玩法失败关闭。纯继续意图未能恢复唯一原始目标时同样失败关闭,不输出以“继续”为标题的兜底产物。
|
||||
- `assets/art-spec.png` 的唯一语义是视觉规范与派生参考,不是运行时背景、角色、目标或图集。game-chat 的核心玩家、方块/目标、障碍/场景和反馈必须来自独立派生的透明 `assets/art-spritesheet.png` 及其服务端 `iconImageSrcs` 本地切片;Runtime 以 `sourceResourceId` 把切片清单绑定到当前图集,并要求活动 Canvas 分别绘制四类不同切片。纯代码核心实体、猜测图集等分坐标、单个裁切冒充全部类别、整图展示、隐藏引用、微小水印和诱饵路径均不构成真实美术使用。`playable-web-game-state.v1.sequence` 只在真实输入、状态迁移或模拟状态变化时递增,不得由纯渲染帧推进。
|
||||
- game-chat canonical 图集进一步要求主图与四个切片都有非空且互不复用的 Canvas `assetObjectId`;同一对象在顶层、resource 与 asset 中重复返回的 `assetObjectId` 和 `taskId` 必须分别一致,冲突时失败关闭。公开 `assets/art-spritesheet-slices/manifest.json` 与私有 `.agent/runtime/art-spritesheet-contract.json` 必须同时绑定 `sourceResourceId / sourceAssetObjectId / sourceTaskId / sourceCanvasProjectId / sourceReferenceResourceIds`,并对四种 usage 的 `name / path / width / height / resourceId / assetObjectId / contentSha256 / pixelSha256` 做完整一致性比较。旧项目缺私有回执时不得从公开文件反向生成回执;只允许同一 game-chat root 下处于 running 的 scheduled `art-asset-plan` 对固定主图执行受限 `replaceExisting=true` repair,普通 pending、其它 Agent、其它路径或有效合同均拒绝。九个固定合同文件在任何 canonical 改动前必须先全部打开可信源句柄,完成有界双读,并在 journal 持久化后、发布 `prepared` marker 前再次整体复读与身份校验;任一文件在九文件捕获窗口变化都失败关闭,不能形成跨版本混合快照。事务控制文件在 Unix 通过锚定目录句柄的 `openat / unlinkat + O_NOFOLLOW` 读取和清理;Windows 以拒绝 delete sharing 的目录句柄阻止 rename / replacement,并以句柄文件身份复核子文件,不能退回 pathname 前后检查。恢复目标必须从 canonical 项目根开始逐组件检查全部祖先,任何中间 symlink / reparse point 都拒绝。CAS 安装先把目标字节持久化到同目录私有 staging,再以不覆盖的原子 hard-link / rename 切换;即使 canonical 已安装后清理 backup/staging 才报错,也必须把当前项纳入同轮逆序回滚。Canvas 资产登记成功并写 `committed` marker 后才可清理;恢复继续在同一项目写锁内完整验证全部 journal 条目和快照、形成内存计划并缓存全部 canonical 的恢复前状态,再按 transaction id 整组回滚或幂等清理。回滚前必须 CAS 证明目标仍是本轮安装结果,外部修改不得被覆盖,冲突进入 reconciliation。远端下载阶段不得长期持锁,也不得在无锁 request 阶段修改 canonical 文件。
|
||||
- game-chat canonical 图集进一步要求主图与四个切片都有非空且互不复用的 Canvas `assetObjectId`;同一对象在顶层、resource 与 asset 中重复返回的 `assetObjectId` 和 `taskId` 必须分别一致,冲突时失败关闭。公开 `assets/art-spritesheet-slices/manifest.json` 与私有 `.agent/runtime/art-spritesheet-contract.json` 必须同时绑定 `sourceResourceId / sourceAssetObjectId / sourceTaskId / sourceCanvasProjectId / sourceReferenceResourceIds`,并对四种 usage 的 `name / path / width / height / resourceId / assetObjectId / contentSha256 / pixelSha256` 做完整一致性比较。旧项目缺私有回执时不得从公开文件反向生成回执;只允许同一 game-chat root 下处于 running 的 scheduled `art-asset-plan` 对固定主图执行受限 `replaceExisting=true` repair,普通 pending、其它 Agent、其它路径或有效合同均拒绝。九个固定合同文件在任何 canonical 改动前必须先全部打开可信源句柄,完成有界双读,并在 journal 持久化后、发布 `prepared` marker 前再次整体复读与身份校验;任一文件在九文件捕获窗口变化都失败关闭,不能形成跨版本混合快照。事务控制文件在 Unix 通过锚定目录句柄的 `openat / unlinkat + O_NOFOLLOW + O_NONBLOCK` 读取和清理,FIFO 等非普通文件不得阻塞恢复;恢复前态在同一叶子句柄稳定双读并复核元数据、内容和当前 inode。Windows 祖先目录 pin 只请求读访问并拒绝 delete sharing,允许九文件捕获重复持有同一 root;只有事务叶子句柄请求删除访问。事务目录创建、合同源捕获与恢复 CAS 必须从 canonical 项目根句柄逐组件打开或创建父目录,staging、hard-link、no-replace move 与 unlink 全部相对固定父目录句柄执行,不能退回 pathname 预检后操作。CAS 安装先把目标字节持久化到同目录私有 staging,再以不覆盖切换安装;即使 canonical 已安装后清理 backup/staging 才报错,也必须把当前项纳入同轮逆序回滚。Canvas 资产登记成功并写 `committed` marker 后才可清理;恢复继续在同一项目写锁内完整验证全部 journal 条目和快照、形成内存恢复计划并缓存全部 canonical 的恢复前状态,逐项安装完成后、清理事务证据前还必须再次复核整组 installed 状态,再按 transaction id 整组回滚或幂等清理。回滚前必须 CAS 证明目标仍是本轮安装结果,外部修改不得被覆盖,冲突进入 reconciliation。远端下载阶段不得长期持锁,也不得在无锁 request 阶段修改 canonical 文件。
|
||||
- 图集本地提交以主图 staging 为线性化前置:任何新主图先写随机私有 staging 文件,替换时保留 previous,canonical 主图完整安装后才写四切片、公开清单、私有回执和项目资产登记。进程若在 backup/install 窗口退出,同一 accepted External generation 恢复先识别唯一同 suffix 的 previous/replacement 对并恢复旧主图,再按远端结果完成替换;若 canonical 已等于远端摘要,则不再要求替换授权,直接补齐其余合同。成功后清理主图、四切片、公开清单、私有回执和项目 manifest 的全部遗留 staging/backup。首次生成也禁止直接流式写 canonical 路径,避免部分 PNG 被误认为已安装结果。
|
||||
- 俄罗斯方块任务固定使用 `BrowserPlaytestScenario::TetrisV1`。`playable-web-game-state.v1.gameplay` 必须持续提供 `kind=tetris`、`activePieceId`、`rotation`、`row`、`lockedPieces`、`lineClearChecks`、`clearedLines` 和 `occupiedCells`;任何采样点删除字段都立即失败,但允许 `score / nextPieceId` 等额外 telemetry。静态连续性检查按 HTML 规定的五种空白解析标签,并在 `type` 存在时忽略 legacy `language`,再结合 `nomodule` 判断可执行脚本。JavaScript / ESM 必须先通过 Oxc parser 与 semantic;无效语法失败关闭,import/export production、ASI、default / namespace / alias / re-export 链接、template `${...}` 内表达式、注释与正则边界都以 AST 为权威,不得跨换行猜测 `from` 或把未链接模块当完成证据。函数定义、表达式体箭头、调用可达性和参数 / 局部遮蔽按 semantic symbol identity 判断;guard return / throw 只终止其真实控制流分支,不能截断后续可达玩法。字符串、注释、HTML raw-text/RCDATA 与其它非执行容器、带 `src` 脚本的内联正文、非 JavaScript script、短路动态 import、恒真分支的 else、顶层无条件 return / throw 后正文和 `if(false)` / 明显恒假分支诱饵继续不构成证据;filter / splice 消行仍必须由满行判断真实控制,并作用于正式棋盘。本地 `.js / .mjs`、inline module 与传递依赖统一限制在 `game/`,文件按去重数量并受 256 文件、累计 2 MiB 上限约束。浏览器因果探针运行于 Chromium 隔离执行上下文,Promise 闭包保存点击前基线,MutationObserver 只冻结 trusted 输入 listener 及其点击派生微任务产生的最后状态;宿主只有在 Chromium 元素鼠标输入成功完成后才调用隔离世界 finish,把该 CDP 结果作为完整手势证据,页面无法伪造。这样更早注册的 `window` capture listener 即使 `stopImmediatePropagation()`,以及后注册的同步 click listener,都不会造成假阴性;RAF / timer 仍不会污染证据。探针 fingerprint 覆盖 install、ready 与 finish 的真实脚本。其余同方块旋转、重力/锁定、四格落盘或消行、`lineClearChecks` 和 restart 归零约束保持不变。旧/续跑合同及 game-chat 快车道在回执读取前按有效原任务迁移到该场景、重算 fingerprint 并回读一致,旧 generic-v1 回执视为 stale,不能交付完成。
|
||||
- 俄罗斯方块任务固定使用 `BrowserPlaytestScenario::TetrisV1`。`playable-web-game-state.v1.gameplay` 必须持续提供 `kind=tetris`、`activePieceId`、`rotation`、`row`、`lockedPieces`、`lineClearChecks`、`clearedLines` 和 `occupiedCells`;任何采样点删除字段都立即失败,但允许 `score / nextPieceId` 等额外 telemetry。静态连续性检查按 HTML 规定的五种空白解析标签,并在 `type` 存在时忽略 legacy `language`,再结合 `nomodule` 判断可执行脚本。JavaScript / ESM 必须先通过 Oxc parser 与 semantic;无效语法失败关闭,import/export production、ASI、default / namespace / alias / import 后再 export 的 bridge / re-export 链接、template `${...}` 内表达式、注释与正则边界都以 AST 为权威,不得跨换行猜测 `from` 或把未链接模块当完成证据。跨模块 alias 投影保留 origin 根绑定,只改写解析到 import symbol 的 importer reference span;object shorthand 展开为显式键值以保留原键,namespace 只改写完整 member span。同文本对象属性和局部遮蔽不得连带改写,投影依赖也只由 semantic 未解析根引用递归纳入。函数定义、表达式体箭头、调用可达性和参数 / 局部遮蔽按 semantic symbol identity 判断;guard return / throw 只终止其真实控制流分支,不能截断后续可达玩法。字符串、注释、HTML raw-text/RCDATA 与其它非执行容器、带 `src` 脚本的内联正文、非 JavaScript script、短路动态 import、恒真分支的 else、顶层无条件 return / throw 后正文和 `if(false)` / 明显恒假分支诱饵继续不构成证据;filter / splice 消行仍必须由满行判断真实控制,并作用于正式棋盘。本地 `.js / .mjs`、inline module 与传递依赖统一限制在 `game/`,文件按去重数量并受 256 文件、累计 2 MiB 上限约束。浏览器因果探针运行于 Chromium 隔离执行上下文,Promise 闭包保存点击前基线,MutationObserver 只冻结 trusted 输入 listener 及其点击派生微任务产生的最后状态;宿主只有在 Chromium 元素鼠标输入成功完成后才调用隔离世界 finish,把该 CDP 结果作为完整手势证据,页面无法伪造。这样更早注册的 `window` capture listener 即使 `stopImmediatePropagation()`,以及后注册的同步 click listener,都不会造成假阴性;RAF / timer 仍不会污染证据。探针 fingerprint 覆盖 install、ready 与 finish 的真实脚本。其余同方块旋转、重力/锁定、四格落盘或消行、`lineClearChecks` 和 restart 归零约束保持不变。旧/续跑合同及 game-chat 快车道在回执读取前按有效原任务迁移到该场景、重算 fingerprint 并回读一致,旧 generic-v1 回执视为 stale,不能交付完成。
|
||||
- 泥点不足是确定性业务中断,不是瞬态 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 重放,禁止分别执行原响应和持久响应。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user