@shundoo-ai/dsh-cosmic 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shundoo-ai/dsh-cosmic",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "苍穹助手:金蝶云苍穹ERP的开发与数据分析,涵盖表单元数据建模、插件开发与热部署、数据可视化报告。",
5
5
  "keywords": [
6
6
  "dsh",
@@ -104,7 +104,7 @@ description: >
104
104
  > |---|---|---|
105
105
  > | PID 定位 | `Get-NetTCPConnection` → `Get-CimInstance`(**`jps` 看不到 SYSTEM 账户的 cosmic**) | `lsof -tiTCP:<port> -sTCP:LISTEN` 或 `jps -l \| grep JettyServer` |
106
106
  > | classpath 分隔符 | `;` | `:` |
107
- > | 沙箱 | 受限沙箱禁跨进程管道 → 端口查询 / attach 会被拒(`CreatePipe error=5`),需把命令合并后放开 | 无此限制 |
107
+ > | 沙箱 | 受限沙箱**砍令牌特权**(实测只剩 `SeChangeNotifyPrivilege`)→ 端口查询被拒、attach 缺 `SeDebugPrivilege`(见 F4);构建的 `CreatePipe error=5` 是另一条(见 F3) | 无此限制 |
108
108
  > | shell / 变量 | PowerShell(DSH 兜底到 5.1,注意默认 ANSI 解码);`$env:VAR=` | bash/sh;`export VAR=` |
109
109
  > | 构建可执行 | `gradle.bat`(按 wrapper 定版) | `gradle`(按 wrapper 定版) |
110
110
  > | 缓存预热命令 | `robocopy`(见 R3.2) | `cp -R`(见 R3.2) |
@@ -157,7 +157,7 @@ $proc.CommandLine # 必须含 kd.bos.service.webserver.JettyServer
157
157
 
158
158
  - ⚠️ **Windows 上 `jps` 看不到 cosmic**(实测):cosmic 常由 **SYSTEM** 账户启动,而 `jps` 只列举**当前用户**的 JVM → 输出为空,**极易误判「cosmic 没启动」**。用端口反查。
159
159
  - 定位后必查命令行是否含 `-XX:+DisableAttachMechanism` —— 带此参数的 JVM **无法 attach**,直接停。
160
- - 跨用户 attach **可用、无需提权**(Windows JVM attach 命名管道对 Administrators 组放行);但目标进程若是 **elevated(管理员启动)**,普通权限 Attacher 会 `拒绝访问`(见 F4)。
160
+ - attach 到 cosmic(**SYSTEM 属主**,与调用方**异属主/异 IL**)需要**打开目标进程句柄**的特权,判据就是调用方令牌带不带 **`SeDebugPrivilege`**(**`whoami /priv` 一眼可判**)。DSH 受限沙箱(`workspace-write`)/ 未提升 / UAC 过滤令牌,**都只是"缺这个特权"的不同成因** → `openProcess` 报 `拒绝访问`。⚠️ **别写"无需提权"**(早期结论已被证伪;本机 `EnableLUA=0` 恒带该特权,才看起来"不需要")。
161
161
  - 同机多个 cosmic 进程,认命令行区分(例:`mservice-cosmic\lib` + `-agentlib:jdwp=…address=5005` = 热部署目标;`mservice\lib` + `-Dmonitor.dev.model=debug` = 另一套)。**勿杀错进程。**
162
162
 
163
163
  #### R1.3 定位模块目录与产物
@@ -198,6 +198,7 @@ $GRADLE = Get-ChildItem "$env:USERPROFILE\.gradle\wrapper\dists\gradle-$VER-bin"
198
198
  ```
199
199
 
200
200
  > ⚠️ 机器上可能并存要求 Java 22 的新版 Gradle —— 取"最新"会把构建带偏。
201
+ > ⚠️ **dist 可能只在两处之一**:系统 `~/.gradle/wrapper/dists/`(`$env:USERPROFILE\.gradle\wrapper\dists\`)**或**工作区 `<Gradle根>/.gradle/user-home/wrapper/dists/`(AGENTS.md 约定的 `GRADLE_USER_HOME`)。上面两段示例都只搜了系统那处 —— **两处都搜,命中即用**。
201
202
 
202
203
  #### R2.3 agent jar(用机器级缓存副本,**不要**直接用 preset 原件)
203
204
 
@@ -256,41 +257,48 @@ try { & $GRADLE ":<模块>:build" -x test --console=plain } finally { Pop-Locati
256
257
  | 有 javac 的 `deprecation` / `unchecked` 提示 | 真编译 | 正常 |
257
258
  | `BUILD SUCCESSFUL` 但 `build/classes` 为空 | 伪成功 | 同上,重跑 |
258
259
 
259
- > 真编译判据(三条同时看):① `compileJava` 后**没有** `UP-TO-DATE`/`FROM-CACHE`;② 出现 javac 的 `deprecation`/`unchecked`;③ `build/classes` 下 class 的 mtime 是**刚刚**。
260
+ > 真编译判据:**① 与 ③ 是硬判据** —— ① `compileJava` 后**没有** `UP-TO-DATE`/`FROM-CACHE`;③ `build/classes` 下 class 的 mtime 是**刚刚**。
261
+ > **② 只是旁证**:出现 javac 的 `deprecation`/`unchecked` 提示可佐证真编译,但**该文件没触发这类警告时就不会出现 —— 不代表没编译**(实测:1 个 class 真编译,①③ 成立而 ② 为空)。**别把 ② 当必要条件**,否则会误判"没编译"。
260
262
  > ⚠️ 本模板 `gradle.properties` 带 `org.gradle.caching=true`,`clean` 后仍可能 `FROM-CACHE`(macOS 实测 **879ms**、Windows 约 **2 秒** 跑完)。**要真编译必须加 `--no-build-cache`**(必要时再加 `--rerun-tasks` 强制重跑整任务图 —— 注意它在大模块上会连 jar transform 一起重跑,见 R3.2)。
261
263
  > 🔬 **一个可复用的诊断手法**:想逼出"真实 javac"又不想全量重编,可**删掉某一个产出的 `.class`** 再构建 —— 如果它 **1~2 秒就从 `FROM-CACHE` 还原**,说明构建缓存(`org.gradle.caching=true`)在起作用、**javac 依然没跑**;此时要真编译必须 `--no-build-cache`。(实测:Windows 1.5s `FROM-CACHE`。)
262
264
 
263
265
  #### R3.2 构建耗时:先看"该花多少"的基准,再判异常
264
266
 
265
- **实测基准**(Windows,模块 ~100 class,编译类路径 **4497 jar**;同机同 daemon):
267
+ **实测基准**(Windows;模块 ~100 class;编译类路径 **4497 jar** —— 该数是 **ymprj 工作区 `mservice-cosmic/lib` 目录的 jar 总数**,与 slow-windows 文档 §4.4 的"4008 个文件(khxty 工作区、模块 `compileClasspath` 文件数)"**口径与工程都不同**,别混;**同机,跨若干 daemon** —— 正是因为"daemon 新旧"被证明不是因子,才把两次全新 daemon 的对照都列进来):
266
268
 
267
269
  | 场景 | Wall | javac 真跑了吗 |
268
270
  |------|------|----------------|
269
271
  | no-op(只做配置 + UP-TO-DATE 检查) | **1.4s** | 否 |
270
272
  | 删产物 class → 构建缓存还原 | **1.5s** | 否(`FROM-CACHE`) |
271
- | 真 javac(1 个文件,daemon 已预热) | **4.7s** | 是 |
272
- | 完整 `:build`(1 个文件变更) | **3~4s** | 是 |
273
- | 完整 build + 热部署(daemon 已预热) | **3.1s** | 否/是 |
274
- | 同上,但**该 daemon 生命周期内第一次真编译** | **~140s** | 是 |
273
+ | 真 javac(1 个文件,**距上次真编译 1–8 分钟**) | **3~4.7s** | 是 |
274
+ | **全新 daemon**、缓存热(距上次真编译 **8 分钟**) | **8.75s** | 是 |
275
+ | **全新 daemon**、缓存热(距上次真编译 **46 分钟**) | **8.65s** | 是 |
276
+ | 真 javac,**距上次真编译 13.5 小时**(daemon 新旧**无关**) | **~139s** | 是 |
277
+
278
+ > 🔥 **热窗口(实测边界)**:间隔 **≤1 小时**仍为 ~9s 量级(8min→8.75s、**46min→8.65s**,几乎无变化);**13.5h → ~139s**。⇒ 冷热分界落在 **46 分钟 ~ 13.5 小时**之间(未进一步二分);**"全新 daemon"不是变慢的原因**(上表两行全新 daemon 都很便宜)。
275
279
 
276
280
  **两种"冷"必须分开,判据不同:**
277
281
 
278
282
  | 触发 | 判据 | 量级 | 处置 |
279
283
  |---|---|---|---|
280
284
  | ① 快照**从零建**(新工作区 / 新类路径) | `javaCompile/classAnalysis.bin` **持续增长** | 20 分钟级(4000+ jar / 1.9 GB) | 搬运现成快照(见下),或预热一次后等 |
281
- | ② daemon 首次真编译的一次性开销(**疑为快照载入,未直证**) | `classAnalysis.bin` 已存在(实测 ~370MB)**且不再增长**,而 **wall ≫ 进程 CPU**(实测 140s wall / 累计 ~37s CPU → **≥3/4 的时间不烧 CPU**;**阻塞点未直证** —— 可能在 I/O,也可能在锁) | **~140s,每个 daemon 生命周期只付一次** | 等它跑完(**不要 kill**);之后稳定 3–5s |
285
+ | ② 冷缓存的一次性开销(**触发是「距上次真编译的间隔」,与 daemon 是否新建无关**) | `classAnalysis.bin` 已存在(实测 ~370MB)**且不再增长**,而 **wall ≫ 进程 CPU**(139s wall / 累计 ~30s CPU → **≥3/4 时间不烧 CPU,阻塞型**;对照热态那次是 **CPU-bound**:36s 生命期烧 35.3s CPU) | **~140s(间隔小时级)**;**间隔 ≤1 小时**则 ~9s 量级(实测 8min→8.75s、46min→8.65s) | 等它跑完(**不要 kill**) |
282
286
 
283
- > 🔁 **独立复现**(另一 daemon、另一日期,**同机同工作区**;源文件 `<Gradle根>/.gradle/user-home/daemon/<ver>/daemon-28568.out.log`):9/23 该 daemon **首次构建 157s** → 同一 daemon 第 2 次构建 **8.7s**。与今天的 139.7s → 3.1s 同一形态 → 结论「每个 daemon 生命周期只付一次」不是偶发。
284
- > ⚠️ 边界:9/23 那次只留存了 wall(构建期 daemon 日志 0 行、客户端输出未留存),**无法确认当次 javac 是否真跑**,所以写"首次构建"而非"首次真编译";上条结论不受影响。
285
- > ⚠️ 这 ~140s 的**成因**目前只到「现象吻合」层次:371 MB 分析数据的首次载入,叠加 AV 实时扫描 / 虚拟化磁盘(本机为 Hyper-V 虚拟化环境),**未做 A/B**。别把它写成已证结论。
287
+ > ⚠️ **归因修正(2026-09-24 实测,推翻早前写法)**:曾据两个数据点写成「每个 daemon 生命周期只付一次」,被**后续数据点证伪** —— 已有**两个**"全新 daemon 却很便宜"的独立反例(12:59 启动的 8.75s、13:44 启动的 8.65s,后者距上次真编译 **46 分钟**),而另一 daemon 在**隔 13.5 小时**后花了 139s。
288
+ > 四点数据:间隔 **13.5h → 139s**(阻塞型)|**46min → 8.65s**|**8min → 8.75s**|**1–8min → 3–4s**。→ **相关因子是"距上次真编译的间隔",不是 daemon 寿命**。
289
+ > 🔁 **跨日复现(唯一的非当日样本)**:`daemon-28568`(**9/23** 22:49)**首次构建 157s → 同一 daemon 第 2 次 8.7s**,与今日同一形态 ⇒ 该现象**不是当日偶发**。⚠️ 该次只留存 wall(构建期 daemon 日志 0 行、客户端输出未留存),**无法确认当次 javac 是否真跑**,故记"首次构建"而非"首次真编译"。
290
+ > 成因**未定**:OS page cache 冷读(371 MB 快照被换出)与 Gradle 缓存状态需重新校验,**两者都能解释现有数据,没有可区分二者的证据** —— 不要写成已证结论。
291
+ > 内存已排除:实测该机 **65 GB 总量 / 40.6 GB 可用**(也印证删掉 `-Xmx` 那条建议是对的)。
292
+ > 源文件可追溯:`<Gradle根>/.gradle/user-home/daemon/<ver>/daemon-<pid>.out.log`(如 `daemon-33008`(46min→8.65s)/ `daemon-1944`(8min→8.75s)/ `daemon-7524`(13.5h→139s)/ `daemon-28568`(9/23 跨日复现:首次构建 157s → 第 2 次 8.7s))。
286
293
 
287
294
  规则:
288
295
 
289
- - **不要 `--no-daemon`**(`org.gradle.daemon=true` 默认开)—— 否则这笔 ~140s 预热每次都要重付。
290
- - **daemon 重启 / 被回收后,先跑一次哑编译预热**(如 `:<模块>:compileJava`),之后每次改代码 → build + 热部署都在 3–5s。
291
- - 判"卡住"必须**两个信号同时看**:进程 CPU 增量 ≈ 0 **且** `classAnalysis.bin` 不再增长。只看"没有输出"会把 ② 误判成死锁(这条是实测踩出来的)。
296
+ - **不要 `--no-daemon`**:每次构建都要付 JVM 冷启动 + 配置开销,量级**因项目而异**:本工作区实测全新 daemon 冷启动 **3–5s**(13.9s 总 wall − daemon 内构建 8.75s,含客户端握手与本手册 R0–R2 发现 ~1.9s);另一工作区数据是 `--no-daemon` 42s vs 常驻 32s(**Δ≈10s**,见 `cosmic-dev-best-practices/references/cosmic-hotdeploy-build-slow-windows.md` §4.7/§5.1)。⚠️ 别引用 "+30s"(该文档早前版本有此处,与它自己的数据表不符,已修正)。方向不变:**保留常驻 daemon**;另⚠️ 本条与 ② 无关 —— 早前写的"否则 140s 每次都要重付"基于已被证伪的 daemon 寿命归因。
297
+ - **隔了几小时没编译后的第一次构建会慢(~140s),属正常**,别误判成卡死、也别急着重启 daemon。想把它与正式构建分开观察时,可以先跑一次 `:<模块>:compileJava` 把冷开销付掉再跑构建。
298
+ - 🔬 **量构建 wall 的两个仪表,含义不同,别混用**:① Gradle 自报 `BUILD SUCCESSFUL in Ns` = 客户端观测的整次构建;② `daemon-<pid>.out.log` 里成对的 `has started executing the build` / `has finished executing the build` = **daemon 侧真正执行构建的时段**。实测同一次构建两者可差几秒(**8.75s vs 12s**,差在客户端握手/启动)。做分档归因(如 ② 的冷开销)用 ②,报告用户感知耗时用总 wall。注意:该文件**只有生命周期标记、没有构建期内容日志**(见下条)。
299
+ - 判"卡住"必须**两个信号同时看**:进程 CPU 增量 ≈ 0 **且** `classAnalysis.bin` 不再增长。只看"没有输出"会把冷开销误判成死锁(这条是实测踩出来的)。
292
300
  - **"低 CPU 不是活干在别的进程"的佐证**:Gradle 7.6 默认在 daemon 进程内执行 javac(未显式配 `options.fork`),且实测构建后 `Get-Process java` 只有 Gradle daemon + cosmic + MC 三个 JVM、**无额外 worker**(Gradle worker daemon 默认 idle 3 小时不会自动退,若 fork 过必然还在)。
293
- - ⚠️ **别用 daemon 的 `.out.log` 去证明这件事**:客户端连着构建时,daemon 会把日志**全部转发给客户端**,`.out.log` 在构建期间**一行都不落** —— 日志里 `Starting process / fork` 计数为 0 是**结构性为零**,不是"观测到没 fork"(实测两份 daemon 日志在构建时段各 0 行;唯一像"编译"的行出现在 daemon **关闭**时释放缓存锁,属假阳性)。
301
+ - ⚠️ **别用 `.out.log` 里 `Starting process / fork` 计数去证明"是否 fork"**:客户端连着构建时,daemon 把**内容日志全部转发给客户端**,`.out.log` 在构建期间**一行内容都不落** —— 那些计数为 0 是**结构性为零**,不是"观测到没 fork"(实测两份 daemon 日志在构建时段各 0 行;唯一像"编译"的行出现在 daemon **关闭**时释放缓存锁,属假阳性)。该文件里**只有生命周期标记**(上面那条仪表)是可用的。
294
302
  - ⚠️ **`--rerun-tasks` 非必要不加**:它会强制重跑**整个任务图**(含 `jar transform`),在大模块上更慢,且实测在 `org.gradle.parallel=true` 下撞过 `AsyncCacheAccessDecoratedCache` 的跨进程缓存锁等待(54 线程 parked、守护进程 CPU≈0,12 分钟无进展)。怀疑缓存欺骗时,先试 `clean` + `--no-build-cache`(R3.1),仍不够再考虑它。
295
303
  - 🔍 **别把这条与 ② 的 ~140s 合并成一条结论**:本条由 `--rerun-tasks` 触发,且跨**进程**锁需要 ≥2 个 Gradle 进程 —— 实测 ② 发生时机器上只有 **1 个** Gradle 进程(另有 cosmic、MC),条件不满足,两者是不同现象。
296
304
  - 可选缓解(**未直证,仅现象吻合**):把安装根(含 `mservice-cosmic/lib`)与 `<Gradle根>/.gradle` 加入 **Defender 实时扫描排除**(需管理员)—— 411 MB 分析文件首次读入被 AV 拦一遍,与百秒级等待吻合,但没有做过 A/B。
@@ -350,7 +358,8 @@ try { & $GRADLE ":<模块>:build" -x test --console=plain } finally { Pop-Locati
350
358
  [ ] PID 的命令行含 kd.bos.service.webserver.JettyServer(没杀错进程)
351
359
  [ ] jar 是绝对路径、且是本次构建产物(时间戳/大小对得上)
352
360
  [ ] account_id / tenant_id 非空
353
- [ ] 沙箱已放开(Windows 受限沙箱下跨进程 attach 会被拒)
361
+ [ ] 调用方令牌带 `SeDebugPrivilege`(`whoami /priv` 一眼可判)—— **attach 到 SYSTEM 属主的 cosmic 时,缺它必报 `拒绝访问`**;**同用户同 IL 的目标(如自己起的 dev JVM)则不需要**(见 F4 与 slow-windows §7.10)
362
+ —— 受限沙箱 / 未提升 / UAC 过滤令牌都会造成缺它;⚠️ `EnableLUA=0` 时"提升"是空操作
354
363
  ```
355
364
 
356
365
  ### R5 验证
@@ -370,9 +379,9 @@ try { & $GRADLE ":<模块>:build" -x test --console=plain } finally { Pop-Locati
370
379
  |---|-------------|------|------|
371
380
  | F1 | `Unexpected UTF-8 BOM` | 凭证文件带 BOM,某处用裸 `utf-8` 读 | 去 BOM;**别再手编辑该 JSON,改走 `erp-config.py write`** |
372
381
  | F2 | `does not contain a Gradle build` | 执行构建的 cwd 不是 Gradle 根 | 按 R1.1 切到含 `settings.gradle` 的目录 |
373
- | F3 | `[Win]` `拒绝访问` / `CreatePipe error=5`(端口查询或 attach 阶段) | 沙箱未放开 | 把「体检 + 构建 + 注入」合成**尽量少的命令**并带 `danger-full-access` 重发;**不要先单独跑体检**(白花一次授权) |
374
- | F4 | `[Win]` attach 报 `拒绝访问`(`WindowsVirtualMachine.openProcess`) | 目标进程是 elevated(管理员启动) | 提升 Attacher 权限执行(`Start-Process -Verb RunAs`);先确认目标是否 elevated |
375
- | F5 | `jps` 查不到 / 端口反查为空 | ① Windows 上 cosmic 由 SYSTEM 启动 ② 端口错 ③ 沙箱拒绝 | 先用 R0.3 的 HTTP 探测区分"活着"与"查不到";活着 → 用 R1.2 端口反查 |
382
+ | F3 | `[Win]` **端口查询 / 起子进程**阶段的 `拒绝访问` / `CreatePipe error=5`(**不含 attach** —— attach 一律走 F4) | 沙箱未放开 | 把「体检 + 构建 + 注入」合成**尽量少的命令**并带 `danger-full-access` 重发;**不要先单独跑体检**(白花一次授权)。⚠️ **若会话审批提示已被禁用**(approval=never)而策略仍为 `workspace-write`,则**无法自行放开** → 构建/注入 fail-closed,**须先请用户恢复策略或授权**再继续 |
383
+ | F4 | `[Win]` attach 报 `拒绝访问`(`WindowsVirtualMachine.openProcess`)——**与 F3 同因(受限令牌)、不同失败点**:本条的 Attacher **已起来**,卡在打开句柄 | **调用方令牌缺"打开目标进程"的权限**:判据 = `whoami /priv` 里有没有 **`SeDebugPrivilege`**(沙箱受限令牌 / 未提升 / UAC 过滤令牌都只是缺它的成因;cosmic 常为 SYSTEM 属主,故必须有)。**已证**:沙箱外用 `runas /trustlevel:0x20000` 摘掉特权、**IL 未变**亦复现同一签名 ⇒ 与"跨完整性级别"无关(实验全文 + 复现命令见 slow-windows §7.14) | ① 在**同一 shell** 跑 `whoami /priv`:**无**该特权 → 放开沙箱(`danger-full-access`)或提升后原样重跑;**有**仍失败 → 查集合外原因(attach socket 残留 / 目标 JVM 未就绪 / `DisableAttachMechanism`);② ⚠️ `EnableLUA=0` 时 `RunAs` 是空操作 —— 此时**最常见**的缺特权来源是沙箱,但**不止它**(显式降权启动如 `runas /trustlevel:0x20000` 同样会造成) |
384
+ | F5 | `jps` 查不到 / 端口反查为空或报 `CimException: 拒绝访问` | ① Windows 上 cosmic 由 SYSTEM 启动 ② 端口错 ③ **沙箱拒绝**(受限沙箱下 `Get-NetTCPConnection` 会被拒) | 先用 R0.3 的 HTTP 探测区分"活着"与"查不到";活着 → 用 R1.2 端口反查;若反查被**沙箱**拒(`CimException: 拒绝访问`)→ **放开 `danger-full-access` 重跑**(注意:受限沙箱下运行手册会**止步于 R0.4**,要单测 Attacher 见 slow-windows §7.14 的复现命令) |
376
385
  | F6 | `Could not find tools.jar` | `JAVA_HOME` 指向 JRE(或被注册表带偏) | 显式设 `JAVA_HOME` 到含 `lib/tools.jar` 的 JDK |
377
386
  | F7 | `AgentLoadException` | Attacher 与目标 JVM 大版本不一致 | 用目标进程自己的 JDK(R2.1 第 1 顺位) |
378
387
  | F8 | 构建长时间无输出 | 多半是冷缓存建快照,不一定是死锁 | 用 R3.2 判据;确卡住再清 Gradle 守护进程重试(**不要动 cosmic 进程**) |
@@ -16,11 +16,13 @@ Caused by: java.io.IOException: Cannot run program "…\jdk1.8.0_281\bin\java.ex
16
16
  CreatePipe error=5, 拒绝访问
17
17
  ```
18
18
 
19
- - 根因:DSH Windows 沙箱(受限令牌)**禁止受限进程创建跨进程管道**(CreatePipe);Gradle fork daemon、
20
- JVM attach(热部署 Attacher)均触发,`--no-daemon` 也无效(仍 fork single-use daemon)
19
+ - 根因:DSH Windows 沙箱(受限令牌)下 **Gradle fork daemon 会撞 `CreatePipe error=5`**(`--no-daemon` 也无效:仍 fork single-use daemon)。
20
+ ⚠️ 但**"起子进程 / 跨进程管道被一刀切禁止"不成立**(2026-09-24 实测):`cmd /c "echo PIPE_OK"` 在 `workspace-write` 下**成功**;早期见到的 `Program 'NETSTAT.EXE' failed to run: 拒绝访问` 触发条件**未钉死** —— **别把这条写成普遍规则**,逐例判断。
21
+ - ⚠️ **与热部署 Attacher 的失败不是同一个失败点**(2026-09-24 实测修正):Attacher 进程**能正常起来**(会打印 `attaching to pid=…`),是在 `openProcess`(打开目标进程句柄)时才被拒 —— 轴是**令牌特权**(见 §7.14),与 `CreatePipe` 这条无关,**别合并叙述**。
21
22
  - 解决:构建/热部署命令须**单命令级放开权限**(`danger-full-access`,仅该命令生效)或会话级放宽;
22
23
  放开后本文的增量排查才可进行
23
- - 区分:`CreatePipe error=5` = 权限问题(跑不起来);本文第 3/4 节 = 增量失效(跑得慢)——先排除前者
24
+ - 区分:`CreatePipe error=5` = 权限问题(跑不起来);本文第 3/4 节 = 增量失效(跑得慢)——先排除前者。
25
+ 注:**attach 的 `拒绝访问` 是另一条**(`openProcess` 阶段、轴是令牌特权),见 §7.14,别与本条的 `CreatePipe` 混。
24
26
 
25
27
  ---
26
28
 
@@ -106,9 +108,16 @@ Gradle 增量编译被禁用时,任何一次源码改动都会触发全量重
106
108
  ### 4.1 修改根 `cosmic/build.gradle`
107
109
 
108
110
  ```groovy
109
- // subprojects 块内,trd 依赖排除嫌疑 jar
111
+ // subprojects 块内,trd 依赖排除嫌疑 jar。
112
+ // ⚠️ 清单**必须按 §4.8 的全量扫描重算**(版本敏感,勿照抄早期清单):
113
+ // 早期清单里的 bcprov-jdk18on-1.78.1.jar 在当前 trd 中已**不存在**(现为 1.82)——
114
+ // 这种 exclude 属于**静默空排除**:不报错、也不生效,最难排查。
115
+ // 下列为 2026-09 实测集合,仅作对照(log4j-core 是注解处理器那条,其余是含 Java17+ 类的):
116
+ // log4j-core-*.jar | bcprov-jdk18on-*.jar | hive-service-*.jar
117
+ // | ImpalaJDBC42-*.jar | jackson-core-*.jar | jsch-*.jar
110
118
  api fileTree(dir: trd, include: '*.jar',
111
- exclude: ['ImpalaJDBC-2.6.3.jar', 'log4j-core-2.20.0.jar', 'bcprov-jdk18on-1.78.1.jar'])
119
+ exclude: ['log4j-core-2.20.0.jar', 'bcprov-jdk18on-1.82.jar', 'hive-service-4.2.0.jar',
120
+ 'ImpalaJDBC42-2.6.38.jar', 'jackson-core-2.16.2.jar', 'jsch-0.2.26.jar'])
112
121
 
113
122
  // allprojects 块内,JavaCompile 关闭注解处理(可选项,非增量必需——
114
123
  // 实测去掉后增量编译仍生效,仅在全量编译时省 ~10s javac 处理器扫描时间;本项目最终去掉,保留最小改动)
@@ -123,7 +132,8 @@ tasks.withType(JavaCompile) {
123
132
  ```groovy
124
133
  api fileTree(trd) {
125
134
  include '*.jar'
126
- exclude 'qing-arthas-*.jar', 'ImpalaJDBC-2.6.3.jar', 'log4j-core-2.20.0.jar', 'bcprov-jdk18on-1.78.1.jar'
135
+ // ⚠️ 按 §4.8 的全量扫描结果重算后再填(下面是 2026-09 实测集合,版本变了就作废)
136
+ exclude 'qing-arthas-*.jar', 'log4j-core-2.20.0.jar', 'bcprov-jdk18on-1.82.jar', 'hive-service-4.2.0.jar', 'ImpalaJDBC42-2.6.38.jar', 'jackson-core-2.16.2.jar', 'jsch-0.2.26.jar'
127
137
  }
128
138
  ```
129
139
 
@@ -185,7 +195,8 @@ allprojects {
185
195
  $gradle ":<模块>:showCompileCp" -I "<path>\showcp.gradle" --console=plain
186
196
  ```
187
197
 
188
- > 实测:排除前 4008 个文件且 3 个嫌疑 jar 都在;排除后 4005 个文件 `(none - exclusion OK)`。
198
+ > 实测(**khxty 工程,2026-08**):排除前 4008 个文件且 3 个嫌疑 jar 都在;排除后 4005 个文件 `(none - exclusion OK)`。
199
+ > 注:这个 **4008** 是该模块 `compileClasspath` 的**文件数**,与"`mservice-cosmic/lib` 目录下 jar 总数"(另一工作区 ymprj 实测 **4497**,见 SKILL.md R3.2)**口径不同、环境也不同**,两个数字不矛盾,比较时别混。
189
200
 
190
201
  ### 4.5 扫描注解处理器(PowerShell)
191
202
 
@@ -226,12 +237,18 @@ $gradle = "C:\Users\<user>\.gradle\wrapper\dists\gradle-7.6.3-bin\<hash>\gradle-
226
237
  > `~/.gradle/wrapper/dists` 下最新」。本机同时存在 `gradle-8.8-bin`,它要求 Java 22
227
238
  > 运行时,与本项目 JDK 8 + tools.jar 链路不兼容,会直接把构建带偏。
228
239
  >
240
+ > ⚠️ **gradle.bat 可能只在两处之一**:系统 `$env:USERPROFILE\.gradle\wrapper\dists\` **或**
241
+ > 工作区 `<Gradle根>\.gradle\user-home\wrapper\dists\`(AGENTS.md 约定的 `GRADLE_USER_HOME`)。
242
+ > 上面示例只写了前者 —— **找不到就改搜后者**(两个都搜更稳,见 `cosmic-vibe-coding/SKILL.md` R2.2)。
243
+ >
229
244
  > 🧩 完整流程(端口反查 PID / JDK 选择 / Gradle 定版 / 构建 / 注入)见
230
245
  > `cosmic-vibe-coding/SKILL.md` 的**运行手册(R0–R5)**,其中已内置上述 JDK、Gradle 版本、
231
246
  > PID 发现、account/tenant 读取逻辑,以及失败分支表(F1–F10)。
232
247
 
233
- > ❌ 不要加 `--no-daemon`:每次 fork 单次 Daemon(JVM 冷启动 + 重新加载配置 ≈ +30s)
248
+ > ❌ 不要加 `--no-daemon`:每次 fork 单次 Daemon(JVM 冷启动 + 重新加载配置都要重付)
234
249
  > ✅ 复用常驻 Daemon(`org.gradle.daemon=true` 默认开启),改几行代码约 5-10 秒
250
+ >
251
+ > ℹ️ **量级以本项目实测为准,别引用 "+30s"**:本项目数据表(§5.1)是 `--no-daemon` **42s** vs 常驻 **32s** → **Δ≈10s**;本文「误区层」的结论也是「Daemon 只占几秒,大头是全量编译本身」。另一工作区(ymprj)实测全新 daemon 冷启动仅 **3–5s**。早前此处写的 "+30s" 与本文自身数据不符,已修正。
235
252
 
236
253
  ### 4.8 全量扫描定位「Java 17+ 类」jar(推荐,避免打地鼠)
237
254
 
@@ -363,9 +380,10 @@ $jar = "<模块>\build\libs\<模块>-1.0.0.jar"
363
380
  - Windows classpath 分隔符用 **`;`**(不是 `:`)
364
381
  - 成功标志:退出码 0 + `SUCCESS: N jar(s) deployed & reloaded`
365
382
 
366
- > ⚠️ 两层权限(叠加时先放开沙箱再处理 elevated):
367
- > ① DSH 受限沙箱下 JVM attach 触发 `CreatePipe error=5`,须单命令级放开 `danger-full-access`;
368
- > ② cosmic 若以 elevated(管理员)启动,Attacher 需 `Start-Process -Verb RunAs` 提升执行,否则 `拒绝访问`(见 §7.11)。
383
+ > ⚠️ **权限:attach 需要"能打开目标进程句柄"的特权** —— 一眼判据是调用方令牌带不带 **`SeDebugPrivilege`**(`whoami /priv`)。
384
+ > 沙箱受限令牌 / 未提升 / UAC 过滤令牌,**都只是"缺这个特权"的不同成因**;缺了就在 `openProcess` 报 `拒绝访问`(见 §7.14)。
385
+ > 处置:**先放开沙箱(`danger-full-access`)原样重跑**;仍不行再提升 Attacher(⚠️ `EnableLUA=0` 时提升是空操作)。
386
+ > 注:目标进程与调用方**同用户同 IL**(自己起的 dev JVM)时不带该特权也能 attach —— cosmic 这种 **SYSTEM 属主**则必须有。
369
387
  - 原理:`ExtJarApi.save(字节)` 写 `t_meta_ext_jar` 表 → `ExtJarApi.reset(accountId)` 重载 ExtClassLoader
370
388
  - 不重启 cosmic、不碰 mc;jar 无需拷到 `lib/cus`
371
389
 
@@ -378,7 +396,7 @@ $jar = "<模块>\build\libs\<模块>-1.0.0.jar"
378
396
  改完依赖务必用自定义任务打印真实 compileClasspath 验证
379
397
  4. **改 build.gradle 后首次构建必全量**,属正常;之后才增量
380
398
  5. **`Get-NetTCPConnection` 在非管理员权限下会误报"无监听"**,用 `Invoke-WebRequest` 探测兜底
381
- 6. **build 前确认 Daemon 活着**:`gradle --status` 应见 `IDLE` 条目;`--no-daemon` 是慢构建常见元凶(+30s 冷启动)
399
+ 6. **build 前确认 Daemon 活着**:`gradle --status` 应见 `IDLE` 条目;`--no-daemon` 会为每次构建重付 JVM 冷启动 + 配置(本项目实测 Δ≈10s;另一工作区全新 daemon 冷启动 3–5s —— **别引用"+30s"**,见 §4.7 注)
382
400
  7. **识别全量重编**:改一行后 class 文件时间戳全变 = 全量;只有个别变 = 增量正常;
383
401
  多次构建间隔短时用「最新时间戳 ±30s」窗口精确切分,避免混入前一次全量产物
384
402
  8. **`dependencies --configuration compileClasspath` 对 fileTree 是惰性展开**,不一定显示 jar 明细,
@@ -387,28 +405,43 @@ $jar = "<模块>\build\libs\<模块>-1.0.0.jar"
387
405
  (`F:\cosmic\xinghan8\jdk\bin\java ... -DappName=cosmic ... kd.bos.service.webserver.JettyServer`),
388
406
  而 `jps` 只列举当前用户的 JVM → 输出里完全没有它,容易误判「cosmic 没启动」。
389
407
  用 §6.1 方式零(按端口反查 OwningProcess)替代。
390
- 10. **跨用户 attach 是可用的,无需提权**:cosmic 跑在 SYSTEM 下、Attacher 由
391
- `lihaisheng\administrator` 启动,实测 attach 成功(Windows JVM attach 命名管道对
392
- Administrators 组放行),不必 `Start-Process -Verb RunAs`。
408
+ 10. **attach 到 cosmic(SYSTEM 属主)是*需要特权*的 —— 早期"跨用户 attach 无需提权"的结论已被证伪**(2026-09-24 判决性测试):
409
+ - **实际判据**:调用方令牌是否带 **`SeDebugPrivilege`**(它绕过目标进程 DACL)—— `whoami /priv` 一眼可判。
410
+ - **为什么以前看起来"无需提权"**:本机 `EnableLUA = 0`(UAC 关闭)→ Administrator 进程**恒为 High IL 且默认带该特权**,所以每次 attach 都成功 —— **并非因为"不需要特权"**。
411
+ - **在 UAC 启用的机器、或 DSH 受限沙箱(`workspace-write`)下,该特权缺失 → `openProcess` 报 `拒绝访问`**。
412
+ - 精确表述:**需要能打开目标进程句柄**(目标与调用方**异属主/异 IL** ⇒ 需 `SeDebugPrivilege`;同用户同 IL 则不需要)。
413
+ **判决实验**:`runas /trustlevel:0x20000` 摘掉特权后,**完整性级别未变(仍 High)却复现同一签名** ⇒ 与"跨 IL"无关,轴是特权。
393
414
  11. **`-XX:+DisableAttachMechanism` 是硬阻断**:本机 IDEA 侧另一个 JVM(`CosmicStudio-runnable.jar`)
394
415
  带此参数,对它 attach 必然失败;发现 PID 后先扫命令行里有没有这个 flag。
395
- 9. **`java_home` 以 IDEA 项目 SDK 为准**(Windows 常见 `C:\Program Files\java\jdk`),
416
+ 12. **`java_home` 以 IDEA 项目 SDK 为准**(Windows 常见 `C:\Program Files\java\jdk`),
396
417
  不要误填 cosmic 自带 JDK 目录;Attacher 只需与 cosmic 同 JDK 大版本(同为 JDK8 即兼容)
397
- 10. **gradle 构建命令不要加 `--no-daemon`**;多 IDLE Daemon 并存无害,`--stop` 可一次性清理
398
- 11. **重启 cosmic 后热部署 attach 报 `拒绝访问`(管理员权限坑)**:
399
- - 现象:`[reloadjar] FAILED: java.io.IOException: 拒绝访问`(`WindowsVirtualMachine.openProcess`)
400
- - 原因:cosmic 以 **elevated(管理员)** 权限重启后,普通权限 Attacher 无法 attach
401
- (Windows attach 要求调用方 ≥ 目标进程权限);旧进程普通权限启动时无此问题
402
- - 解决:用 `Start-Process -Verb RunAs` 提升执行 Attacher(UAC 确认),详见
403
- `cosmic-jdwp-debug-config.md` §9.3
404
- - 诊断口诀:attach 报 `拒绝访问` → 查目标进程是否 elevated → 是则提升 Attacher
405
- 12. **重启 cosmic 的连带影响**:PID 变化 + `t_meta_ext_jar` 清空(热部署状态失效),
418
+ 13. **gradle 构建命令不要加 `--no-daemon`**;多 IDLE Daemon 并存无害,`--stop` 可一次性清理
419
+ 14. **attach 报 `拒绝访问`(`WindowsVirtualMachine.openProcess`)—— 判据是 `SeDebugPrivilege`,不是"先试着提权"**:
420
+ - 现象:`[reloadjar] FAILED: java.io.IOException: 拒绝访问`(Attacher 进程**已正常起来**,卡在打开目标进程句柄)
421
+ - **原因(单一机制)**:attach 到 cosmic(常为 **SYSTEM** 属主,与调用方**异属主**)需要**打开目标进程句柄**的特权,实际判据就是调用方令牌带不带 **`SeDebugPrivilege`**(它绕过目标 DACL)。
422
+ **沙箱受限令牌 / 未提升 / UAC 过滤令牌,都只是"缺这个特权"的不同成因** —— 不要再按"沙箱 vs elevated"拆成两个候选,它们不是并列原因。
423
+ > **🎯 判决性证据(2026-09-24 三点实验)**:① `danger-full-access`:令牌含 `SeDebugPrivilege` → **SUCCESS**;② **沙箱外**用 `runas /trustlevel:0x20000`(同用户、摘掉管理员):令牌只剩 `SeChangeNotifyPrivilege`、**完整性级别仍是 High(未变)** → **同一签名 `拒绝访问`**;③ DSH `workspace-write`:令牌同样只剩 `SeChangeNotifyPrivilege` → **同一签名**。
424
+ > ⇒ **轴是「令牌特权」,不是「完整性级别」**(②里 IL 未变仍被拒),**也不是沙箱独有**(②在沙箱外复现)。
425
+ - 🔍 **一眼可判的观测**:失败时在**同一个 shell** 跑 **`whoami /priv`** ——
426
+ **在** `SeDebugPrivilege` → 不是特权问题,往集合外查(attach socket 残留 / 目标 JVM 未就绪 / `DisableAttachMechanism`);
427
+ **不在** → 就是它:**放开沙箱(`danger-full-access`)或提升后原样重跑**。
428
+ - ⚠️ **`EnableLUA = 0` 的机器上"提升"是空操作**(Administrator 恒为 High IL、恒带该特权):既解释了历史上"不用提权也成功",也意味着此时**最常见的**缺特权来源是沙箱(`workspace-write`)——**但不止它**:显式降权启动(`runas /trustlevel:0x20000`,即判决实验②,**沙箱外**)同样会造成。
429
+ - ⚠️ **与 §0 的 `CreatePipe error=5` 不是同一个失败点**:那个发生在**起子进程**阶段(Gradle fork),本条发生在**打开句柄**阶段;同因(受限令牌)不同环节,签名与处置都不同。
430
+ - 提升处置(仅当 `whoami /priv` 无该特权且本机 UAC 启用):`Start-Process -Verb RunAs`,详见 `cosmic-jdwp-debug-config.md` §9.3。
431
+ - 🧪 **复现命令**(会话策略切到 `workspace-write` 后)。⚠️ 此时**运行手册会在 R0.4(端口反查)就失败**(`Get-NetTCPConnection` → `CimException: 拒绝访问`),走不到 R4,所以要**直接调 Attacher**:
432
+ ```powershell
433
+ whoami /priv # 期望:只剩 SeChangeNotifyPrivilege(无 SeDebugPrivilege)
434
+ $jdk='<安装根>\jdk'; $agent=Join-Path $HOME '.dsh\cache\cosmic\agent\kd-reloadjar-agent-d8ca9d80.jar'
435
+ & "$jdk\bin\java.exe" -cp "$jdk\lib\tools.jar;$agent" kd.reloadjar.Attacher <PID> "$agent" "<jar>" <account_id> <tenant_id>
436
+ ```
437
+ 15. **重启 cosmic 的连带影响**:PID 变化 + `t_meta_ext_jar` 清空(热部署状态失效),
406
438
  重启后需重新 build + 热部署;新 PID 用 wmic 查 `-DappName=cosmic` 的 JettyServer 进程确认
407
- 13. **新版 trd jar 会带 Java 21+ 类,排除清单按实际扫描定**:bcprov 1.82 / jackson-core 2.16.2 /
439
+ 16. **新版 trd jar 会带 Java 21+ 类,排除清单按实际扫描定**:bcprov 1.82 / jackson-core 2.16.2 /
408
440
  jsch 0.2.26 / ImpalaJDBC42 2.6.38 / hive-service 4.2.0 共 5 个 jar 含 major≥65 的类(见 4.8),
409
441
  旧文档只排除 3 个 jar 的方案在新版本下不够——必须用 4.8 的全量扫描一次性定位
410
- 14. **排除 jar 后仍报旧类无法分析 = 增量快照缓存坑**:`build/tmp/compileJava/previous-compilation-data.bin`
442
+ 17. **排除 jar 后仍报旧类无法分析 = 增量快照缓存坑**:`build/tmp/compileJava/previous-compilation-data.bin`
411
443
  复用旧 classpath 分析结果,`clean`(或重启 daemon)后重建基线即恢复增量(见 4.9)
412
- 15. **先分清"跑不起来"还是"跑得慢"**:DSH 受限沙箱(workspace-write)下构建直接报
413
- `CreatePipe error=5 拒绝访问`(Gradle fork daemon / JVM attach 被拦)是**权限问题**,须单命令级放开
414
- `danger-full-access`(`--no-daemon` 无效),**不是**本文的增量失效问题;放开后仍慢再按第 3/4 节排查
444
+ 18. **先分清"跑不起来"还是"跑得慢"**:DSH 受限沙箱(workspace-write)下**构建**直接报
445
+ `CreatePipe error=5 拒绝访问`(**Gradle fork daemon** 被拦)是**权限问题**,须单命令级放开
446
+ `danger-full-access`(`--no-daemon` 无效),**不是**本文的增量失效问题;放开后仍慢再按第 3/4 节排查。
447
+ ⚠️ **热部署 attach 不走这条**:它报的是 `openProcess` + Java 栈的 `拒绝访问`(见 §7.14),别混。