@zhushanwen/pi-ext-guards 0.5.0 → 0.5.1

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": "@zhushanwen/pi-ext-guards",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Shared runtime-environment guards for Pi extensions — oncePerProcess(key, fn) dedupes cross-session side-effect operations against pi factory re-invocation and handler accumulation (shared library, not a Pi extension, zero dependencies).",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -21,6 +21,6 @@
21
21
  },
22
22
  "scripts": {
23
23
  "typecheck": "npx tsc --noEmit",
24
- "test": "vitest run"
24
+ "test": "vitest run --pool=threads"
25
25
  }
26
26
  }
@@ -86,7 +86,7 @@ describe("oncePerProcess", () => {
86
86
  });
87
87
 
88
88
  const first = oncePerProcess("dedupe:async-inflight", fn);
89
- // 落定前第二次调用:同一 Promise 实例(在飞去重不双跑)
89
+ // 落定前第二次调用:同一 Promise 实例(在途去重不双跑)
90
90
  const second = oncePerProcess("dedupe:async-inflight", fn);
91
91
  expect(second).toBe(first);
92
92
  // 先 attach 断言再消费,避免 unhandledRejection
@@ -2,17 +2,17 @@
2
2
  //
3
3
  // guardStaleCtx 单测——验收条款逐条对应(crash-resilience u1-ext-guard):
4
4
  // a. 前置代际检查:isCtxStale=true → fn 不执行、stale 静默降级(返回 undefined)
5
- // b. 非 stale 错误原样上抛(守卫不吞真实 bug,同步 throw 与 async rejection 双面)
5
+ // b. 非 stale 错误原样上抛(检查不吞真实 bug,同步 throw 与 async rejection 双面)
6
6
  // c. onStale 回调:前置命中无参调用;文案分诊命中携带错误调用;缺省走 TAIJI_AGENT_DEBUG=1 stderr
7
7
  // d. 文案子串匹配:STALE_CTX_MARKER 命中即静默(isCtxStale=false 时文案兜底仍生效)
8
8
  // e. async fn:stale rejection → resolve undefined;非 stale rejection → 原样 reject
9
- // f. 正常路径:返回值/resolve 值原样透传(守卫不改变正常路径,A2 负面验证的单测面)
9
+ // f. 正常路径:返回值/resolve 值原样透传(检查不改变正常路径,A2 负面验证的单测)
10
10
 
11
11
  import { describe, expect, it, vi, afterEach } from "vitest";
12
12
 
13
13
  import { STALE_CTX_MARKER, guardStaleCtx } from "../index.ts";
14
14
 
15
- /** pi 实装 stale 文案的完整形态(E1 崩溃堆栈原文,探针 PS-30 守卫其稳定性)。 */
15
+ /** pi 实装 stale 文案的完整形态(E1 崩溃堆栈原文,探针 PS-30 检查其稳定性)。 */
16
16
  const PI_STALE_ERROR = `This extension ctx is stale ${STALE_CTX_MARKER} or reload. Do not use a captured pi or command ctx after ctx.newSession().`;
17
17
 
18
18
  afterEach(() => {
@@ -20,7 +20,7 @@ afterEach(() => {
20
20
  vi.restoreAllMocks();
21
21
  });
22
22
 
23
- describe("guardStaleCtx 前置代际检查(G1 形态)", () => {
23
+ describe("guardStaleCtx 前置代际检查", () => {
24
24
  it("isCtxStale=true:fn 完全不执行,stale 静默降级返回 undefined", () => {
25
25
  const fn = vi.fn(() => "should not run");
26
26
 
@@ -56,7 +56,7 @@ describe("guardStaleCtx 前置代际检查(G1 形态)", () => {
56
56
  });
57
57
  });
58
58
 
59
- describe("guardStaleCtx 同步抛错分诊(F2 形态同步面)", () => {
59
+ describe("guardStaleCtx 同步抛错分诊(catch 分诊的同步侧)", () => {
60
60
  it("非 stale 错误:原样上抛(同一错误实例,不吞不包装)", () => {
61
61
  const boom = new Error("real bug: schema validation exploded");
62
62
  const onStale = vi.fn();
@@ -108,7 +108,7 @@ describe("guardStaleCtx 同步抛错分诊(F2 形态同步面)", () => {
108
108
  });
109
109
  });
110
110
 
111
- describe("guardStaleCtx async fn(F2 形态异步面,scheduler tick 迁移形态)", () => {
111
+ describe("guardStaleCtx async fn(catch 分诊的异步侧,scheduler tick 迁移形态)", () => {
112
112
  it("stale rejection:resolve 为 undefined(不 reject,fire-and-forget 安全)", async () => {
113
113
  const onStale = vi.fn();
114
114
 
@@ -140,7 +140,7 @@ describe("guardStaleCtx async fn(F2 形态异步面,scheduler tick 迁移形
140
140
  });
141
141
 
142
142
  describe("guardStaleCtx onStale 缺省(默认 debug 日志)", () => {
143
- it("TAIJI_AGENT_DEBUG=1:stderr 写入守卫降级行(含 label 与错误文案)", () => {
143
+ it("TAIJI_AGENT_DEBUG=1:stderr 写入检查降级行(含 label 与错误文案)", () => {
144
144
  vi.stubEnv("TAIJI_AGENT_DEBUG", "1");
145
145
  const stderr = vi.spyOn(process.stderr, "write").mockReturnValue(true);
146
146
 
@@ -1,18 +1,20 @@
1
1
  /**
2
- * PS-30 探针:pi ExtensionRunner 的 stale ctx 错误文案契约(crash-resilience D1 守卫
2
+ * PS-30 探针:pi ExtensionRunner 的 stale ctx 错误文案契约(crash-resilience D1 检查
3
3
  * 分诊词锁,docs/pi-semantics.json PS-30)。
4
4
  *
5
- * 断言方式(静态直读 dist,同 packages/runtime pi-semantics-prompt-rejection.test.ts 范式):
5
+ * 断言方式(静态直读 dist,同 packages/runtime pi-semantics-prompt-rejection.test.ts 范式;
6
+ * 文案检查串用 ext-guards STALE_CTX_MARKER 常量插值而非硬编码字面量——pi 侧文案与
7
+ * 常量任一侧漂移都会红,对拍链双侧闭合):
6
8
  * - runner.js:`invalidate()` 默认 message 含 "stale after session replacement"
7
9
  * (staleMessage 的唯一写入点)+ `assertActive()` 的 `throw new Error(this.staleMessage)`
8
10
  * 形态(E1 崩溃堆栈帧 `assertActive → sendUserMessage` 的实装机制)。
9
11
  * - loader.js:pi API(sendMessage / sendUserMessage / appendEntry)的 `assertActive()`
10
- * 前置——守卫分诊词覆盖「跨 session 存活回调触碰捕获 pi」的面。
11
- * - runner.js:ctx 的 compact / abort / shutdown 受 assertActive 保护——守卫分诊词
12
- * 同样覆盖「回调触碰捕获 ctx」的面。
12
+ * 前置——检查分诊词覆盖「跨 session 存活回调触碰捕获 pi」的范围。
13
+ * - runner.js:ctx 的 compact / abort / shutdown 受 assertActive 保护——检查分诊词
14
+ * 同样覆盖「回调触碰捕获 ctx」的范围。
13
15
  *
14
16
  * 该文案是 ext-guards `guardStaleCtx` 的兜底分诊依据(代际检测为主判,本文案为
15
- * reload 盲区兜底)。pi 升级红 = 文案/机制漂移:守卫退化为「全部上抛」(回到现状
17
+ * reload 盲区兜底)。pi 升级红 = 文案/机制漂移:检查退化为「全部上抛」(回到现状
16
18
  * 崩溃链路,不更危险),同步 ext-guards STALE_CTX_MARKER 与本文档引用后更新 verifiedWith。
17
19
  *
18
20
  * 运行:cd extensions/shared/ext-guards && npx vitest run src/__tests__/pi-semantics-stale-ctx-wording.test.ts
@@ -21,6 +23,8 @@ import { describe, it, expect } from "vitest";
21
23
  import { existsSync, readFileSync } from "node:fs";
22
24
  import { join } from "node:path";
23
25
 
26
+ import { STALE_CTX_MARKER } from "../index.ts";
27
+
24
28
  /** 定位实装 pi-coding-agent dist(cwd 逐级上溯,同 runtime 探针范式)。 */
25
29
  function locatePiCodingAgentDist(): string | null {
26
30
  let dir = process.cwd();
@@ -46,10 +50,10 @@ const LOADER_SRC = PI_DIST ? readFileSync(join(PI_DIST, "core", "extensions", "l
46
50
  describe.skipIf(!PI_DIST)(
47
51
  `PS-30 探针:stale ctx 错误文案与 assertActive 机制${SKIP_REASON ? `|skip:${SKIP_REASON}` : ""}`,
48
52
  () => {
49
- it("invalidate() 默认 message 含 stale after session replacement(守卫兜底分诊词的 pi 侧源头)", () => {
53
+ it("invalidate() 默认 message 含 stale after session replacement(检查兜底分诊词的 pi 侧源头)", () => {
50
54
  expect(
51
- RUNNER_SRC.includes("stale after session replacement"),
52
- "PS-30 漂移:stale 默认文案消失/变更——guardStaleCtx 文案兜底分诊失效退化为全部上抛(回到现状崩溃链路),同步 ext-guards STALE_CTX_MARKER 后更新 verifiedWith",
55
+ RUNNER_SRC.includes(STALE_CTX_MARKER),
56
+ "PS-30 漂移:pi stale 默认文案与 STALE_CTX_MARKER 不一致——pi 改文案则同步 ext-guards STALE_CTX_MARKER 后更新 verifiedWith;常量被误改则恢复为 pi 实装文案",
53
57
  ).toBe(true);
54
58
  });
55
59
 
@@ -63,26 +67,26 @@ describe.skipIf(!PI_DIST)(
63
67
  ).toBe(true);
64
68
  });
65
69
 
66
- it("pi API(sendMessage/sendUserMessage/appendEntry)前置 assertActive(守卫覆盖捕获 pi 的触碰面)", () => {
70
+ it("pi API(sendMessage/sendUserMessage/appendEntry)前置 assertActive(检查覆盖捕获 pi 的触碰范围)", () => {
67
71
  for (const method of ["sendMessage(message, options)", "sendUserMessage(content, options)", "appendEntry(customType, data)"]) {
68
72
  const idx = LOADER_SRC.indexOf(method);
69
73
  expect(idx, `PS-30 漂移:loader.js pi API ${method} 消失/改签名——复核锚点`).toBeGreaterThanOrEqual(0);
70
74
  const window = LOADER_SRC.slice(idx, idx + 160);
71
75
  expect(
72
76
  window.includes("assertActive()"),
73
- `PS-30 漂移:pi API ${method} 不再前置 assertActive——stale 触碰面改形,复核守卫分诊词的覆盖面`,
77
+ `PS-30 漂移:pi API ${method} 不再前置 assertActive——stale 触碰范围改形,复核检查分诊词的覆盖范围`,
74
78
  ).toBe(true);
75
79
  }
76
80
  });
77
81
 
78
- it("ctx 的 compact/abort/shutdown 受 assertActive 保护(守卫覆盖捕获 ctx 的触碰面)", () => {
82
+ it("ctx 的 compact/abort/shutdown 受 assertActive 保护(检查覆盖捕获 ctx 的触碰范围)", () => {
79
83
  for (const entry of ["compact: (options) => {", "abort: () => {", "shutdown: () => {"]) {
80
84
  const idx = RUNNER_SRC.indexOf(entry);
81
85
  expect(idx, `PS-30 漂移:ctx.${entry.replace(/[: ].*/, "")} 定义消失/改形——复核 createContext 锚点`).toBeGreaterThanOrEqual(0);
82
86
  const window = RUNNER_SRC.slice(idx, idx + 200);
83
87
  expect(
84
88
  window.includes("assertActive()"),
85
- `PS-30 漂移:ctx.${entry.replace(/[: ].*/, "")} 不再受 assertActive 保护——stale 触碰面改形,复核守卫分诊词的覆盖面`,
89
+ `PS-30 漂移:ctx.${entry.replace(/[: ].*/, "")} 不再受 assertActive 保护——stale 触碰范围改形,复核检查分诊词的覆盖范围`,
86
90
  ).toBe(true);
87
91
  }
88
92
  });
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // isRecord / isEnoentError / isErrnoWithCode / isSubagentProcess / isTaijiHost /
4
4
  // isTaijiGuiHost 单测
5
- //(ext-simplify-17 D3/D2/D4 新导出 + isErrnoWithCode errno 守卫上收 +
5
+ //(ext-simplify-17 D3/D2/D4 新导出 + isErrnoWithCode errno 检查上收 +
6
6
  // dmg-r1-7 宿主谓词跨包收敛):
7
7
  // - isRecord 排数组严版:数组必须 false(钉值,防回退到允数组宽版)
8
8
  // - isEnoentError scheduler 严版:仅 Error 子类 + code === "ENOENT" 才 true,
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  //
3
- // @zhushanwen/pi-ext-guards —— pi 运行环境守卫共享库(不是 Pi extension,零依赖纯函数,
3
+ // @zhushanwen/pi-ext-guards —— pi 运行环境检查共享库(不是 Pi extension,零依赖纯函数,
4
4
  // 无 pi SDK peerDep——供任意 @zhushanwen/pi-* 包与子代理核心消费)。
5
5
  //
6
6
  // 背景与设计依据:docs/architecture/file-lock-unification-and-reaper-sink.md §2.2 P3 / §3.2 D3。
@@ -8,7 +8,7 @@
8
8
  // handler 累积注册——session_start handler 的真实派发语义是「每 session × factory
9
9
  // 调用次数」,不是「每 session 一次」。handler 体内的跨 session 副作用操作(写非本
10
10
  // session 的文件 / 注册定时器 watcher / 扫描目录 / 进程操作)因此会被双跑(2026-09-01
11
- // reaper 双跑即此形态)。本包把这类「pi 运行环境隐式坑」的守卫集中一处,业务
11
+ // reaper 双跑即此形态)。本包把这类「pi 运行环境隐式坑」的检查集中一处,业务
12
12
  // extension 引入即用,防线不再散落各包内联。
13
13
 
14
14
  /** 一次执行的结果记录:只缓存成功结果;失败(同步抛错 / Promise rejection)不缓存,下次调用可重试。 */
@@ -25,25 +25,25 @@ const executions = new Map<string, ExecutionRecord>();
25
25
  /**
26
26
  * 进程内按 key 去重地执行 fn:同一 key 至多执行一次**成功**,后续调用重放首次成功结果。
27
27
  *
28
- * 语义细节(设计 §3.2 D3 守卫粒度段 + §3.3 D3 守卫语义;失败语义按加固审查裁决修订
28
+ * 语义细节(设计 §3.2 D3 检查粒度段 + §3.3 D3 检查语义;失败语义按加固审查裁决修订
29
29
  * ——「恢复幂等,下次启动重试」承诺优先,见下方失败段):
30
30
  *
31
31
  * - **结果缓存形态(非跳过)**:首次调用执行 fn 并缓存结果;后续同 key 调用不执行
32
32
  * fn,返回首次的返回值——原样重放:对象返回严格同一引用(toBe 级,非重新求值);
33
33
  * fn 返回 Promise 时重放同一实例。
34
- * - **失败不缓存、可重试**:fn 同步抛错原样上抛(守卫不捕获、不包装)且**不写缓存**;
34
+ * - **失败不缓存、可重试**:fn 同步抛错原样上抛(检查不捕获、不包装)且**不写缓存**;
35
35
  * fn 返回的 Promise rejection 落定后释放 key(仅释放本条失败记录,期间同 key 重试
36
- * 成功的新记录不受影响)——下次同 key 调用重新执行 fn。守卫不自动重试,重试节奏
37
- * 由调用方决定(宿主的其他触发面承接,如下一个 session_start)。代价(有意接受):
36
+ * 成功的新记录不受影响)——下次同 key 调用重新执行 fn。检查不自动重试,重试节奏
37
+ * 由调用方决定(宿主的其他触发点承接,如下一个 session_start)。代价(有意接受):
38
38
  * 首次失败到重试成功之间,factory 二调的双跑窗口重新打开——重试的是**失败过**的
39
39
  * 操作,成功即缓存、窗口随即关闭;对幂等的进程级维护(recoverCrashedRuns「恢复
40
40
  * 幂等,下次启动重试」、配置迁移、索引重建等)这正是期望行为。验收条款「fn 抛错
41
- * 不阻断后续 handler」的准确语义仍是**守卫不吞 fn 的错误**(调用方 catch 守卫调用
41
+ * 不阻断后续 handler」的准确语义仍是**检查不吞 fn 的错误**(调用方 catch 检查调用
42
42
  * 即可继续 handler 后续逻辑,先例:base-tool-enhance runSessionStartMaintenance
43
43
  * 的 try/catch 形态)。
44
44
  * - **粒度边界**:只包「跨 session 副作用操作」(进程级全局维护类,正确频率就是每
45
45
  * 进程至多一次)。session 级幂等操作(如 pending 对账——读当前 session 的 entries
46
- * 与 registry,appendEntry 幂等)必须保持每 session_start 执行,不要挂本守卫;需要
46
+ * 与 registry,appendEntry 幂等)必须保持每 session_start 执行,不要挂本检查;需要
47
47
  * 「每 session 一次」语义的 handler 属另一设计,本包明确不提供。
48
48
  * - **key 是进程内全局命名空间**:跨包共享一个 Map,调用方须用「包名:操作名」前缀
49
49
  * (如 "base-tool-enhance:reap")避免撞 key。
@@ -78,7 +78,7 @@ export function oncePerProcess<T>(key: string, fn: () => T): T {
78
78
  }
79
79
 
80
80
  /**
81
- * 测试专用:清空 oncePerProcess 的进程级守卫 Map。供静态 import 形态的测试重置
81
+ * 测试专用:清空 oncePerProcess 的进程级检查 Map。供静态 import 形态的测试重置
82
82
  * 去重状态(无 resetModules 重建模块图时的等价手段);生产代码禁止调用。
83
83
  */
84
84
  export function _resetOncePerProcessForTest(): void {
@@ -121,7 +121,7 @@ export function isEnoentError(err: unknown): boolean {
121
121
  * `code === 期望值`)。
122
122
  *
123
123
  * 与 isEnoentError 同一收窄纪律(`"code" in err` 零断言——taste/no-unsafe-cast),
124
- * code 参数化:isEnoentError = code 钉死 "ENOENT" 的特化形态。非 Error 对象即使带
124
+ * code 参数化:isEnoentError = code 固定为 "ENOENT" 的特化形态。非 Error 对象即使带
125
125
  * 同名 code 也 false(与 isEnoentError 的严版口径一致——Node fs 抛错恒为 Error 实例)。
126
126
  */
127
127
  export function isErrnoWithCode(error: unknown, code: string): boolean {
@@ -206,7 +206,7 @@ export function isTaijiGuiHost(ctx: GuiHostContextLike): boolean {
206
206
  return isTaijiHost() && ctx.mode === "rpc";
207
207
  }
208
208
 
209
- // ── stale ctx 守卫(guardStaleCtx,崩溃韧性 D1)────────────────────────
209
+ // ── stale ctx 检查(guardStaleCtx,崩溃韧性 D1)────────────────────────
210
210
  //
211
211
  // 背景:pi 的 extension API
212
212
  // 对象(pi / ctx)在 session 替换(newSession/fork/switchSession/reload)后被 runner
@@ -215,7 +215,7 @@ export function isTaijiGuiHost(ctx: GuiHostContextLike): boolean {
215
215
  // timer tick、延迟回调)在该窗口触碰 pi/ctx = pi 进程被无人接的同步 throw 炸死(9/3 E1
216
216
  // 实锤:smart-context compact onError → sendUserMessage → assertActive → exit 1)。
217
217
  // GUI 的每次切 session/新建/重载都在制造失效窗口——这是「pi 单独用不崩、套 GUI 就崩」
218
- // 的核心机制差异。本守卫把 scheduler 已验证的防御范式(G1 代际前置检查 + F2 catch 文案
218
+ // 的核心机制差异。本检查把 scheduler 已验证的防御范式(代际前置检查 + catch 文案
219
219
  // 分诊)泛化成共享原语,接入方不再各自内联。
220
220
 
221
221
  /**
@@ -225,7 +225,7 @@ export function isTaijiGuiHost(ctx: GuiHostContextLike): boolean {
225
225
  * runner.js `invalidate()` 默认 message + `assertActive()`
226
226
  * `throw new Error(this.staleMessage)`;
227
227
  * 探针:extensions/shared/ext-guards/src/__tests__/pi-semantics-stale-ctx-wording.test.ts),
228
- * 随 C-proc-08 pi 版本门禁自动重验。文案变更时守卫退化为「全部上抛」(回到现状崩溃链路,
228
+ * 随 C-proc-08 pi 版本门禁自动重验。文案变更时检查退化为「全部上抛」(回到现状崩溃链路,
229
229
  * 有 pi-crash log 取证,不更危险),门禁报红提示更新分诊词。
230
230
  */
231
231
  export const STALE_CTX_MARKER = "stale after session replacement";
@@ -235,11 +235,11 @@ export interface GuardStaleCtxOptions {
235
235
  /** 观测标签(默认降级日志的前缀,建议 "包名:场景" 形态)。 */
236
236
  label?: string;
237
237
  /**
238
- * 前置代际检查(G1 形态,主判):返回 true 表示本回调所属的 session 已被替换,
239
- * 守卫完全不执行 fn、直接走 stale 降级。调用方注入,同 scheduler 的模块级代数
238
+ * 前置代际检查(主判):返回 true 表示本回调所属的 session 已被替换,
239
+ * 检查完全不执行 fn、直接走 stale 降级。调用方注入,同 scheduler 的模块级代数
240
240
  * 计数器模式(session_start 递增模块级代数,旧代闭包捕获值 < 模块值即 stale)。
241
241
  * 缺省(不注入)恒视为非 stale——前置检查关闭,完全依赖错误文案兜底分诊
242
- * (D1 降级语义声明:无代际计数器的接入方合法形态,文案由 PS-30 门禁守卫)。
242
+ * (D1 降级语义声明:无代际计数器的接入方合法形态,文案由 PS-30 门禁检查)。
243
243
  */
244
244
  isCtxStale?: () => boolean;
245
245
  /**
@@ -251,7 +251,7 @@ export interface GuardStaleCtxOptions {
251
251
  onStale?: (error?: unknown) => void;
252
252
  }
253
253
 
254
- /** PromiseLike 判定(in 收窄,无 cast):守卫据此为 async fn 的 rejection 挂同一分诊。 */
254
+ /** PromiseLike 判定(in 收窄,无 cast):检查据此为 async fn 的 rejection 挂同一分诊。 */
255
255
  function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
256
256
  return (
257
257
  typeof value === "object" &&
@@ -276,19 +276,19 @@ function defaultOnStale(label: string, error?: unknown): void {
276
276
 
277
277
  /**
278
278
  * 包裹「跨 session 生命周期存活的异步回调」:stale ctx 错误静默降级,非 stale 错误
279
- * 原样上抛(守卫不吞真实 bug)。消灭 E1 型崩溃源(crash-resilience D1)。
279
+ * 原样上抛(检查不吞真实 bug)。消灭 E1 型崩溃源(crash-resilience D1)。
280
280
  *
281
281
  * 语义三段(与 scheduler 范式逐条对应,scheduler/runtime.ts startScheduler 是迁移先例):
282
282
  *
283
- * 1. **前置代际检查(G1 形态,主判)**:`opts.isCtxStale?.()` 为 true → 不执行 fn
283
+ * 1. **前置代际检查(主判)**:`opts.isCtxStale?.()` 为 true → 不执行 fn
284
284
  * (完全不触碰捕获的 stale pi/ctx)、调 `opts.onStale?.()`(无参)、返回 undefined。
285
- * 2. **fn 同步执行 + 同步抛错分诊(F2 形态的同步面)**:错误文案含
285
+ * 2. **fn 同步执行 + 同步抛错分诊(catch 分诊的同步侧)**:错误文案含
286
286
  * `STALE_CTX_MARKER`(或抛错后 isCtxStale 翻转)→ 调 `opts.onStale?.(error)`、
287
- * 返回 undefined;**非 stale 类错误原样上抛**——守卫不吞真实 bug(上抛的崩溃链路
287
+ * 返回 undefined;**非 stale 类错误原样上抛**——检查不吞真实 bug(上抛的崩溃链路
288
288
  * 与 E1 相同,有 pi-crash log 取证 + 错误规格表走 T4 恢复链路)。
289
- * 3. **Promise 透传分诊(F2 形态的异步面)**:fn 返回 Promise 时守卫对 rejection
289
+ * 3. **Promise 透传分诊(catch 分诊的异步侧)**:fn 返回 Promise 时检查对 rejection
290
290
  * 挂同一分诊——stale → resolve 为 undefined,非 stale → 原样 reject 给调用方的
291
- * `.catch`。返回的是新 Promise 实例(原实例的 rejection 已被守卫接管,调用方不得
291
+ * `.catch`。返回的是新 Promise 实例(原实例的 rejection 已被检查接管,调用方不得
292
292
  * 再对原实例挂 then/catch,否则非 stale 错误会走原实例 rejection)。
293
293
  *
294
294
  * 分诊谓词 = `isCtxStale?.() || message.includes(STALE_CTX_MARKER)`,与 scheduler
@@ -296,7 +296,7 @@ function defaultOnStale(label: string, error?: unknown): void {
296
296
  * 模块环境后旧闭包代数冻结、isCtxStale 恒 false 的盲区)。
297
297
  *
298
298
  * @param fn 无参回调(闭包捕获业务所需的 pi/ctx——被捕获的是注册时的引用,这正是
299
- * stale 风险的来源,也是守卫存在的理由)
299
+ * stale 风险的来源,也是检查存在的理由)
300
300
  * @returns fn 的返回值;stale 降级路径返回 undefined(前置检查命中时 fn 未执行、无
301
301
  * Promise 可言,故 async 重载的返回类型是 `Promise<R | undefined> | undefined`——
302
302
  * 前置命中即 undefined)。fire-and-forget 调用方直接 `void`;需要接非 stale 错误的
@@ -336,7 +336,7 @@ export function guardStaleCtx(fn: () => unknown, opts?: GuardStaleCtxOptions): u
336
336
  }
337
337
  };
338
338
 
339
- // 1. 前置代际检查:stale 则完全不触碰捕获的 pi/ctx(P-guard-holds 的守卫第一道)
339
+ // 1. 前置代际检查:stale 则完全不触碰捕获的 pi/ctx(P-guard-holds 的检查第一道)
340
340
  if (isStale()) {
341
341
  reportStale();
342
342
  return undefined;