Repository navigation
feat(core): 拆出 #1478 的共享 Core 改动(LSP 失败启动清理、Java 直接启动、归档解压加固、签名向量) - #1724
Merged
Merged
Conversation
从 #1478 拆出 Rust Core、共享契约和 macOS 测试部分,基于最新 preview 重新整理;Windows 宿主改动留在 #1478 后续适配。 - LSP:启动失败的会话进入积压,下次启动前最多再清理一次,清理不了就拒绝新启动; 非法初始配置在启动进程前拒绝;destroy 先确认进程退出再移除会话; 新增 start_lsp_owned 在注册时交出 session id - 进程 dispose:非 Windows 在时限内重复 kill 并轮询退出状态; RemoteProcess 以强制 PROCESS_KILL 回执完成 dispose - Java:javaEntrypoints 不再传作用域,结果只保留工作区拥有的源文件; java.main 带 javaLaunch 时直接用 JDT 路径启动,Maven 只在没有 buildPrepared 回执时补资源步骤 - build.detect:根目录不存在或不是目录时返回空,真实目录的 I/O 错误不再吞掉 - 项目归档解压:预检深度与路径字节上限、只走真实目录、分块复制并检查取消、 区分 Invalid/Interrupted,Windows 拒绝保留设备名 - 插件签名跨平台向量与 macOS 测试;CryptoKit 签名带随机数, macOS 改为用验签证明规范载荷一致 - 同步 rust-core-api 契约与相关 Agent Note - 更新 lsp-lifecycle、run-discovery、build-system-plugins、 plugin-project-generators、declarative-plugin-contract 五项功能矩阵记录 Co-authored-by: Mucheen <1528136628@qq.com>
xiaoyumuxi
marked this pull request as ready for review
November 4, 2026 10:42
Lithe Review结论: ⛔ 有阻塞 Findings
Scope Check
Suggestions
Summary共享 Core 的实现方向和上游复用边界一致,但新直接启动条件没有覆盖当前生产配置格式,造成运行回归。新增测试使用旧 Maven 字段,未覆盖这一关键兼容面。 Verification
|
- process.rs:非 Windows 的 dispose 轮询不再丢弃 terminate_native 的错误, 进程超过期限仍未退出时,把最后一次 kill 失败作为 details 附在 "Language-server root did not exit." 上返回。 - engine.rs:启动失败后的收尾清理属于“同一次失败已经记录过”的例外, 补上 log-exempt 标记写明原因;残留仍由 failed_starts 持有,下一次 启动会先回收并在失败时返回 ProcessFailed。
- ci-windows.yml:real-JDT 通道给 real_jdtls_lists_entrypoints_in_a_plain_java_folder 加 240 秒的 --test-budget。它和 Java 25 用例一样要冷启动真实 JDT LS,Windows run 41817286665 在默认 15 秒看门狗处被终止(无断言失败),同一提交在 macOS arm64 上 6.0 秒通过。 240 秒覆盖用例自身的 90 秒就绪 + 120 秒入口导入期限,其他用例和整套期限不变。 - write-stable-tests 的 windows-and-rust 参考页记录这项放宽的依据。
- build/detect.rs:scan_directory_root 改用 workspace_io::metadata,远程工作区根 (包括经宿主符号链接打开的根)在宿主上判断,不再读本机未实体化的投影而返回空检测。 新增 remote_build_detects_a_root_opened_through_a_host_symbolic_link 回归测试。 - lsp/interface/engine.rs:两条索引策略取消路径先 abandon 启动 attempt 再 session.fail, 取消不再被 fail 记成 Initialize 握手失败。两个既有取消测试附加 startupOperation 并断言没有插件操作记录。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
#1478(
codex/windows-plugin-parity-unified,作者 Mucheen)有 540 个文件,和 preview 冲突,无法直接合入。这个 PR 只拆出其中 Rust Core、共享契约和 macOS 测试 的部分,基于最新 preview 重新整理,review 通过后先合入。Windows 宿主的改动留在 #1478,等这个 PR 合入后再 rebase 适配。Commit 保留了原作者的
Co-authored-by。改动
1. LSP 启动失败后的进程清理(Rust Core)
lsp/interface/engine.rsstart_server启动失败时,如果会话已经注册,就把它的身份记进内部的failed_starts积压,并立即尝试清理一次。返回给调用方的仍是原始错误。cleanup_failed_start()。积压里的会话最多再尝试 dispose 一次,确认退出后才移出积压。仍未退出时,新的启动会以ProcessFailed被拒绝,不会让多个语言服务器进程同时残留。start_server_attempt先调用validate_start_request。非法的初始配置在启动进程之前就被拒绝。启动进程挪到 initialize 请求分配之后。fail("indexingChanged")、abandon、kill_process;第一个路径再dispose(2s),确认退出才移除会话,否则保留注册。destroy先process.dispose(2s)确认进程已经退出,再移除会话。start_server_owned,在spawn_readers之前通过回调把 session id 交给宿主。lib.rs:新增start_lsp_owned(payload, registered),供需要在注册时拿到 session id 的宿主使用(Windows 会用到)。macOS 仍调用原来的入口。lsp/interface/process.rsLanguageServerProcesstrait 增加默认的dispose:只有exit_status()已有结果时才返回 Ok。SystemProcess::dispose:Windows 等进程树在时限内终止;非 Windows 每 10ms 重复一次幂等的terminate_native()(child.kill())并读取退出状态,到截止时间仍在运行就返回 “Language-server root did not exit.”,最后一次 kill 失败的原因作为 details 附在这个错误上。lsp/interface/remote_process.rs:RemoteProcess覆盖dispose,发送强制的PROCESS_KILL(2 秒上限)并记录退出。远程进程树由后端持有,这个强制 kill 回执就是本地能拿到的最终结果。lsp/interface/scripted.rs:测试用的脚本进程支持dispose和block_cleanup,用来模拟清理失败。拆分时在原 PR 基础上补的修正:
dispose忽略时限,只读一次退出状态。kill 发出后子进程要异步回收,紧接着读状态仍可能是“运行中”,destroy会误报失败;没被 kill 过的进程则永远无法 dispose。改成在时限内重复 kill 并轮询。RemoteProcess实现dispose,落到 trait 默认实现后,Remote SSH 的 LSP 会话在destroy时会因为“尚未退出”而失败;补了覆盖实现。Verify Structured Logging,规则rust.let-underscore):process.rs的轮询不再用let _ =丢弃 kill 错误,而是保留最后一次失败并随最终错误返回。engine.rs启动失败后的收尾清理按日志规范第二节第 4 类(同一次失败已经记录过)加log-exempt标记:原始启动错误由本次请求的 Core 失败轨迹记录;清理残留仍由failed_starts持有,下一次启动先回收,回收不了就返回ProcessFailed,由那次请求的轨迹记录。real_jdtls_lists_entrypoints_in_a_plain_java_folder没有配 Windows 用时上限。Windows CI 的 real-JDT 通道默认每项 15 秒,这个用例和 Java 25 用例一样要冷启动真实 JDT LS,run 41817286665 在 15.144 秒被看门狗终止,并非断言失败;本地 macOS arm64 同一提交 6.0 秒通过。ci-windows.yml给它单独加--test-budget …=240000,覆盖用例自身的 90 秒就绪 + 120 秒入口导入期限,其他用例和整套期限不变;依据写进write-stable-tests/references/windows-and-rust.md。build/detect.rs的根目录检查改用workspace_io::metadata,远程工作区根(包括经宿主符号链接打开的根)在宿主上判断,不再读本机未实体化的投影而误报空检测。新增remote_build_detects_a_root_opened_through_a_host_symbolic_link,改回std::fs::metadata时该测试失败。engine.rs两条索引策略取消路径改为先abandon启动 attempt 再session.fail,取消不再被记成插件 Initialize 握手失败。两个既有取消测试附加startupOperation并断言没有插件操作记录,旧顺序下两项都失败。Review P1(
java.main直接启动仍读extensions.maven.mainClass)属于 Java 功能,Java 支持后续整体迁出到独立插件,这个 PR 不处理。对 macOS 的影响: macOS 在收到终止事件后调用一次
lsp.destroyServer并忽略错误(LanguageServerSession.swift、RustCoreBridge.swift),调用方式不变;只是destroy现在最多等 2 秒确认进程退出。2. Java 入口发现与直接启动
lsp/languages/java_entrypoints.rs:java_entrypoints_command()不再传作用域 URI,由 JDT 返回全部入口,Core 只保留打开的工作区拥有的源文件。原来的java_entrypoint_scope_uri删除。lsp/interface/engine.rs:preview 上 Maven profile 等待器的调用点也改成java_entrypoints_command()(这个调用点是原 PR 之后 preview 新增的,拆分时补上)。execution/configuration.rs:java.main带javaLaunch时一律走 JDT 直接启动,不再重新编译单个文件;Maven 只在没有buildPrepared回执时加资源步骤;Gradle 和非托管项目直接用 JDT 给出的路径启动;没有javaLaunch时保留原来的先编译再运行。usesJavaProjectPreparation为 true 时发送javaLaunch,并且总是带buildPrepared: true,所以 Maven 行为不变;Gradle 的java.main现在改用 JDT 路径,这是修正。3. 构建系统检测
build/detect.rs:新增scan_directory_root。工作区根目录不是目录或不存在时返回空结果;根目录是真实目录、但读取出现 I/O 错误时返回错误,不再当成“什么都没检测到”。4. 项目生成器归档解压加固
project_generators/archive.rsvalidate_entries预检全部条目,新增上限:目录深度 64 层(MAX_DEPTH)、路径总字节 16 MiB(MAX_PATH_BYTES)。real_directory):不跟随符号链接,Windows 上也拒绝 reparse point。ExtractionError::Invalid(违反解压约定)和ExtractionError::Interrupted(取消或超时)。safe_component拒绝保留设备名(CON、NUL、COM1等)、非法字符和以点或空格结尾的路径组件;非 Windows 不做这项检查。project_generators/commands.rs:Interrupted作为 Core 错误原样返回。project.extractArchive调用(RustProjectGeneratorOperations.swift)没有取消作用域,行为不变,只是多了预检。5. 插件签名的跨平台测试向量
shared/contracts/plugins/v1/fixtures/signature-vectors.json(新增):固定的 Ed25519 种子、RFC 8032 锚点、规范载荷、文件摘要和内容摘要,包含非 ASCII 文件名。shared/contracts/plugins/v1/README.md:说明这组向量的用途。macos/Tests/LitheTests/PluginSignatureVectorTests.swift(新增):macOS 签名实现用同一组向量验证 RFC 8032 锚点、规范载荷、内容摘要、写读往返后的验签,以及篡改文件后拒绝。拆分时在原 PR 基础上补的修正: 原测试要求 CryptoKit 重新签出和 fixture 逐字节相同的签名,这在 macOS 上不可能通过:CryptoKit 的 Ed25519 签名带随机数,同一私钥对同一消息签两次结果也不同,只有 RFC 8032 的确定性实现(Windows 用的那类)才能复现。原 PR 是 Draft,没跑 macOS CI,所以没暴露出来(本地 4 项里 2 项失败)。现在改成:
primitiveMatchesTheRFC8032Anchor:公钥逐字节一致;RFC 8032 记录的签名能用派生公钥验过;CryptoKit 新签的签名也能验过。recordedSignatureCoversTheSwiftCanonicalPayload(原deterministicSignatureReproducesTheSharedVector):fixture 的payloadBase64和记录签名自洽;Swift 计算的files与 fixture 一致;用 Swift 自己构造的规范载荷去验 fixture 记录的签名,Ed25519 只接受原消息,因此能证明两端规范载荷逐字节一致。6. 文档与 Agent Note
shared/contracts/rust-core-api.md:失败启动清理语义;javaEntrypoints发现范围。.agents/notes/.../2026-09-21-java-entrypoints-owned-by-jdt.md:原 PR 的更新。2026-09-13-language-tooling-and-lsp-runtime-ownership.md:失败启动的积压、最多重试一次、start_lsp_owned的注册时机、非 Windows 轮询与 Windows 进程树等待的区别,并引用对应测试。2026-09-18-java-project-build-and-launch-boundary.md:java.main+javaLaunch的直接启动规则。2026-10-17-build-system-plugin-contract.md:build.detect的空结果边界。7. 平台功能矩阵
verify-platform-feature-matrix-change.sh要求平台实现改动同时更新能力记录。#1478 原来的 16 个矩阵 JSON 主要是 Windows 证据,没有带过来;这里只补本 PR 涉及的 macOS 证据和说明,verificationStatus都保持pending(只有 Core/单测证据,没有复验 macOS 真实 UI):lsp-lifecycle:失败启动积压与 dispose 语义,证据加process.rs、remote_process.rs、scripted.rs,验证动作加 Core 测试命令。run-discovery:javaEntrypoints 发现范围与java.main直接启动,证据加java_entrypoints.rs。build-system-plugins:build.detect空结果边界,证据加tests/build_systems.rs。plugin-project-generators:归档解压的预检与上限,证据加archive.rs。declarative-plugin-contract:签名向量,证据加 fixture 和 Swift 测试。新增测试
native_owner_keeps_failed_initialize_identity_until_verified_disposalwire_owner_retries_failed_initialize_disposal_before_another_startwire_owner_cleans_at_most_one_failed_start_before_rejecting_a_backloginvalid_initial_configuration_is_rejected_before_launchnative_owner_retains_indexing_cancellation_after_registrationthe_command_includes_jdt_invisible_projects_before_workspace_filteringunscoped_results_keep_only_sources_owned_by_the_open_workspacereal_jdtls_lists_entrypoints_in_a_plain_java_folder(真实 JDT)existing_directory_scan_errors_are_not_empty_detectionsdetection_returns_empty_for_non_directory_and_missing_workspace_rootsjava_launch_without_maven_consumes_jdt_paths_without_recompiling_one_filecancellation_between_chunks_stops_before_the_next_write_or_entry、timeout_between_chunks_preserves_its_first_error_and_closes_the_output、rejects_excessive_archive_path_depth_before_creating_any_parent、expanded_byte_budget_counts_actual_written_bytes、implicit_parents_and_path_bytes_share_the_preflight_budget、archive_paths_cannot_hide_traversal_in_normalized_components、command_reports_stable_cancellation_and_timeout_before_any_write、existing_hard_link_target_is_never_truncated_or_replaced、refuses_existing_symlink_directories_without_touching_their_target、windows_archive_paths_reject_ads_device_names_and_verbatim_normalization(仅 Windows)PluginSignatureVectorTests(Swift,4 项)不包含(留在 #1478,后续适配 Windows)
windows/、Plugins/win、scripts/windows、rust/lithe-git-host、Windows Core 测试、shared/i18n词条、Windows fixtures、scripts/{plugins,resources,ci,i18n,architecture}的改动、write-stable-testsSkill、Windows 相关 Note、workflows、official-plugins.json、bun/package 文件,以及 16 个功能矩阵 JSON。这些都依赖 Windows 宿主,单独合入没有意义。本地验证(macOS arm64)
cargo test -p lithe-core:lib 1812 项全部通过,其他测试二进制 1 + 2 + 25 + 5 项通过。real_jdtls_lists_entrypoints_in_a_plain_java_folder和原有的入口点真实测试通过。cargo fmt --check无改动;verify-rust-core-comments、verify-rust-core-static通过。scripts/rust-core/verify-rust-core-swift-bridge.sh:C ABI、链接符号和 Git 执行集成通过。./scripts/macos/test/test-macos.sh --filter PluginSignatureVectorTests:4 项通过(修正前 2 项失败,见第 5 节)。node --test scripts/logging/test-log-events.mjs(15 项)和verify-log-usage.mjs(0 处违规)通过。MacProcessRunnerTests3 项:进程组终止时序,高负载下超时。SettingsSelectPopupGeometryTests1 项:弹出窗口可见性。PluginManagementPresentationTests.javaFeaturesIncludeHostIntegratedCapabilities:依赖Plugins/mac/Official/JavaSupport/plugin.json,这个文件由 feat(plugins): #1495 Maven 模型、配置、测试与服务运行迁移(阶段 D 进行中) #1558 改写,feat(plugins): #1495 Maven 模型、配置、测试与服务运行迁移(阶段 D 进行中) #1558 自己的 Swift tests 也是失败状态;本 PR 不改这个文件、Swift 插件展示代码和 Core 的声明式插件校验。windows/tauri/src-tauri下cargo check --bin lithe-windows):只有 preview 上原有的 2 个错误(project_window_registry.rs:23、:29),本 PR 没有引入新错误。verify-shared-contracts.sh、verify-agent-notes.sh(203 篇)、verify-runtime-bundle-immutability.sh通过。node scripts/i18n/verify-app-language-localization.mjs、node scripts/i18n/generate-i18n.mjs --check通过(本 PR 没有界面文案)。verify-platform-feature-matrix-change.sh origin/preview HEAD、verify-platform-feature-matrix.sh通过。write-stable-tests的test-verify-test-stability.mjs、plan-ci-lane-reuse.test.mjs(20 项)、test-verify-download-cache.mjs通过;CI 报告确认测试名前缀和既有 Java 25 预算一致,能命中。另外:preview 上
windows/tauri/src-tauri/Cargo.lock缺少 lithe-core 已经依赖的unicode-normalization(Windows 编译检查时 cargo 会自动补一行)。这和本 PR 无关,没有带进来。合入后
这个 PR 修改了
shared/contracts/plugins/v1/(README 和新增的signature-vectors.json),合入后需要在 lithe-plugin-docs 运行node scripts/contract.mjs sync --revision <合并后的 preview commit>并提交。