@nightpainters/dsh-ling 1.5.1 → 1.5.3

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.
@@ -1,7 +1,7 @@
1
1
  // E4 访问日志 · 插件侧安装器
2
2
  //
3
3
  // 职责:把日志器挂到 core 补丁预留的两个全局钩子上,并在卸载时干净摘除。
4
- // 不做的事:不碰鉴权判定、不碰记忆与人格数据(任务书 §3.5)。
4
+ // 不做的事:不碰鉴权判定、不碰记忆与人格数据。
5
5
  //
6
6
  // 一个重要性质:**没有补丁也安全**。钩子只是挂在 globalThis 上的可选调用,
7
7
  // 补丁那一行写的是 `globalThis.__dshAccessLogObserve?.(…)` —— 插件在不在、
@@ -28,6 +28,45 @@ export function accessLogHealth() {
28
28
  return active ? active.info() : null;
29
29
  }
30
30
 
31
+ /** 有意关闭(config.accessLog.enabled=false)时由安装器留痕 —— 见 accessLogStatus() 的原因 ①。 */
32
+ let offByConfig = false;
33
+
34
+ /** 补丁状态文件说"两条都 applied 了" ⇒ 补丁在位(缺文件 / 认不出标记都算没在)。 */
35
+ function patchApplied(home) {
36
+ const st = readPatchState(home);
37
+ if (!st || st.marker !== PATCH_MARKER) return false;
38
+ if (!Array.isArray(st.targets) || st.targets.length === 0) return false;
39
+ return st.targets.every((t) => t && t.state === 'applied');
40
+ }
41
+
42
+ /**
43
+ * C-07c(2026-09-27):访问日志的**三态出口** —— 未装时也要说清"为什么没装"。
44
+ *
45
+ * 背景:`audit === null` 曾经同时代表四种情况(老后端 / 有意关闭 / 补丁没打 / 没挂载),
46
+ * 界面上全是空白,分不出"应为但未装"(真·静默失败)与"有意关闭"。本函数把它们分开。
47
+ *
48
+ * @param {{enabled?: boolean, home?: string}} [opts]
49
+ * `enabled`:宿主侧真实配置 `config.accessLog.enabled`(读不到就别传 —— 安装器在**同一进程**里
50
+ * 已经记过 `offByConfig`,跨进程/重启后仍建议由调用方把配置传进来,那才是不靠猜的判据)。
51
+ * `home`:补丁状态文件所在的 $DSH_HOME,测试用;默认 dshHome()。
52
+ * @returns {object} 装上了 ⇒ `{installed:true, ...health 的 11 个字段}`;
53
+ * 没装上 ⇒ `{installed:false, reason}`。
54
+ *
55
+ * 能拿到的三种 reason(按判据强弱排序):
56
+ * ① 'disabled-by-config' —— 安装器记过 offByConfig,或调用方传入 enabled===false。**确定性判据**。
57
+ * ② 'patch-missing' —— 补丁状态文件缺失 / 标记对不上 / 有目标不是 applied。**确定性判据**
58
+ * (补丁真的没打 —— 此时无论插件在不在,请求都不会落盘)。
59
+ * ③ 'not-mounted' —— 补丁在位但日志器不在(没装 / 已卸载 / apply 早退 / 同进程别处覆盖)。
60
+ * 这是**兜底分类**,不是精确诊断:模块内无法把"apply 没跑到"与
61
+ * "apply 跑了但安装器抛了"分开(两者都不留痕)。
62
+ */
63
+ export function accessLogStatus(opts = {}) {
64
+ if (active) return { installed: true, ...active.info().health };
65
+ if (offByConfig || opts.enabled === false) return { installed: false, reason: 'disabled-by-config' };
66
+ if (!patchApplied(opts.home)) return { installed: false, reason: 'patch-missing' };
67
+ return { installed: false, reason: 'not-mounted' };
68
+ }
69
+
31
70
  /** 读补丁状态文件(补丁脚本写的),拿不到就返回 undefined —— 只用于提示,不影响功能。 */
32
71
  export function readPatchState(home = dshHome()) {
33
72
  const file = join(home, 'logs', PATCH_STATE_FILE);
@@ -49,9 +88,12 @@ export function installAccessLog(ctx, cfg = {}) {
49
88
  const options = { ...(cfg || {}) };
50
89
  if (!options.dir) options.dir = join(dshHome(), 'logs');
51
90
  if (options.enabled === false) {
91
+ offByConfig = true; // C-07c:留痕,好让 accessLogStatus() 说出"有意关闭"而不是"没装上"
52
92
  console.info('[dsh-ling] access log off (config.accessLog.enabled=false)');
53
- return () => {};
93
+ // 摘除后不再声称"按配置关闭" —— 否则同一进程里卸载完仍会报 disabled-by-config(会骗人)。
94
+ return () => { offByConfig = false; };
54
95
  }
96
+ offByConfig = false;
55
97
 
56
98
  const log = createAccessLog(options);
57
99
  active = log;
@@ -69,7 +111,7 @@ export function installAccessLog(ctx, cfg = {}) {
69
111
 
70
112
  const info = log.info();
71
113
  console.info('[dsh-ling] access log on; dir=%s deviceCookie=%s', info.dir, info.deviceCookie);
72
- // 补丁没打时钩子永远收不到调用 —— 这是"静默不记"的唯一形态,必须主动喊出来(任务书 §5.3)。
114
+ // 补丁没打时钩子永远收不到调用 —— 这是"静默不记"的唯一形态,必须主动喊出来(就下面这一句 warn)。
73
115
  const state = readPatchState();
74
116
  if (!state || state.marker !== PATCH_MARKER) {
75
117
  console.warn('[dsh-ling] core patch missing → run: node tools/apply-access-log-patch.mjs');
@@ -4,7 +4,7 @@
4
4
  // 而"该插什么、插在哪、怎么认出来"必须只有一处定义 —— 否则
5
5
  // 补丁脚本与插件侧的"补丁在不在"检查会各自漂移。
6
6
  //
7
- // 补丁形态的原则(E4 任务书 §3.5 的延伸):
7
+ // 补丁形态的原则(从本文件"只观察不判定"这条边界延伸出来):
8
8
  // · **一行 hooks**:core 只多一行 `globalThis.__dshAccessLogXxx?.(…)`,
9
9
  // 实现全在本插件里 ⇒ DSH 升级后重打一次,逻辑零改动。
10
10
  // · **可选链兜底**:插件没装 / 被卸载时,这一行是 no-op,DSH 照常启动。