dsh-rule-engine 0.5.11 → 0.5.13
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/README.md +30 -99
- package/lib/core/authorization.js +517 -513
- package/lib/core/config.js +73 -66
- package/lib/core/contract.js +41 -0
- package/lib/core/guard-core.js +754 -724
- package/lib/core/intent.js +11 -3
- package/lib/core/label-fingerprint.js +145 -0
- package/lib/core/lexicon.js +72 -72
- package/lib/core/parser.js +111 -103
- package/lib/core/patterns.js +42 -3
- package/lib/core/state.js +324 -301
- package/lib/core/understander.js +221 -150
- package/lib/index.js +1788 -1663
- package/lib/service.js +17 -2
- package/package.json +1 -1
- package/scripts/publish-aptitude-check.mjs +144 -0
- package/scripts/verify-all.mjs +4 -0
package/lib/service.js
CHANGED
|
@@ -29,6 +29,19 @@ function currentVersion() {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** 仓库 slug(owner/repo):从 package.json repository 解析——通用化后不硬编码作者仓库;
|
|
33
|
+
* 空 = 无法解析(发布包必带 repository,正常路径;异常时检查功能不可用,fail-closed 无害)。 */
|
|
34
|
+
function repoSlug() {
|
|
35
|
+
try {
|
|
36
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
37
|
+
const url = String(pkg.repository?.url || pkg.repository || "");
|
|
38
|
+
const m = url.match(/github\.com[/:]([^/]+\/[^/.]+)/);
|
|
39
|
+
return m ? m[1] : "";
|
|
40
|
+
} catch {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
function parseVersion(v) {
|
|
33
46
|
const s = String(v || "").replace(/^v/i, "");
|
|
34
47
|
const parts = s.split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -97,9 +110,11 @@ class RuleEngineService extends TypertRemoteService {
|
|
|
97
110
|
async checkUpdate() {
|
|
98
111
|
try {
|
|
99
112
|
const current = currentVersion();
|
|
113
|
+
const slug = repoSlug();
|
|
114
|
+
if (!slug) return { ok: false, error: "仓库信息缺失,无法检查更新" };
|
|
100
115
|
const headers = { "User-Agent": "dsh-rule-engine", Accept: "application/vnd.github+json" };
|
|
101
116
|
// P2-8:网络异常/慢速时避免面板挂起
|
|
102
|
-
const releaseRes = await fetch(
|
|
117
|
+
const releaseRes = await fetch(`https://api.github.com/repos/${slug}/releases/latest`, { headers, signal: AbortSignal.timeout(8000) });
|
|
103
118
|
if (!releaseRes.ok) {
|
|
104
119
|
return { ok: false, error: `GitHub Release 检查失败:HTTP ${releaseRes.status}` };
|
|
105
120
|
}
|
|
@@ -109,7 +124,7 @@ class RuleEngineService extends TypertRemoteService {
|
|
|
109
124
|
|
|
110
125
|
let impacts = [];
|
|
111
126
|
try {
|
|
112
|
-
const impactRes = await fetch(
|
|
127
|
+
const impactRes = await fetch(`https://raw.githubusercontent.com/${slug}/main/upgrade-impact.json`, { headers: { "User-Agent": "dsh-rule-engine" }, signal: AbortSignal.timeout(8000) });
|
|
113
128
|
if (impactRes.ok) {
|
|
114
129
|
const data = await impactRes.json();
|
|
115
130
|
if (Array.isArray(data?.versions)) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// publish-aptitude-check.mjs — 发布适用性门禁(T6,2026-08-31)
|
|
2
|
+
// 目的:兑现 README「通用化」承诺的"陌生人视角"检查——发布物必须先证明:
|
|
3
|
+
// ① 无 AGENTS.md(陌生环境冷启动):引擎零错 / 零死映射 / 零意外拦截面
|
|
4
|
+
// ② 空白规则文件:静默(0 规则、无错、无 dead)
|
|
5
|
+
// ③ 任意编号规则(非本机编号):声明式绑定与"纯自证"分流行为正确
|
|
6
|
+
// ④ 四要素 + 执行等级格式的最小规则:actions/handler 解析正确(README 格式契约冒烟)
|
|
7
|
+
// ⑤ 发布物个人标识扫描:files 白名单内文件不得含本机个人标识(jilian/季涟/D:\DeepSeek/邮箱/私人注释)
|
|
8
|
+
// 默认模式 = 单进程纯函数链(不 spawn、不 import index.js,沙箱可直接跑);
|
|
9
|
+
// --deep 模式 = 额外子进程全链冷启动(临时 DSH_HOME + 完整引擎加载 + 真实裁决链,需完整权限/CI)。
|
|
10
|
+
// 用法:node scripts/publish-aptitude-check.mjs [--deep] [--tgz <路径>]
|
|
11
|
+
import { mkdtempSync, writeFileSync, rmSync, readFileSync, readdirSync, statSync, existsSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join, dirname, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const deep = args.includes("--deep");
|
|
19
|
+
const tgzArg = args.includes("--tgz") ? args[args.indexOf("--tgz") + 1] : null;
|
|
20
|
+
|
|
21
|
+
const FAILS = [];
|
|
22
|
+
function check(ok, msg) {
|
|
23
|
+
console.log(`${ok ? "✅" : "❌"} ${msg}`);
|
|
24
|
+
if (!ok) FAILS.push(msg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ①-④ 使用的纯函数链(与引擎 lib 同源,动态 import 保证读到本次代码)
|
|
28
|
+
const { loadRules } = await import("../lib/core/parser.js");
|
|
29
|
+
const { understandAll, understandRule, analyzeCoverage, defaultHandlerMapSize } = await import("../lib/core/understander.js");
|
|
30
|
+
|
|
31
|
+
// ── ① 陌生环境冷启动(临时 DSH_HOME,无 AGENTS.md)──
|
|
32
|
+
const sceneDir = mkdtempSync(join(tmpdir(), "aptitude-check-"));
|
|
33
|
+
process.env.DSH_HOME = sceneDir;
|
|
34
|
+
process.env.DSH_WORKSPACE = root;
|
|
35
|
+
{
|
|
36
|
+
const parsed = loadRules();
|
|
37
|
+
check(parsed.ok === false && parsed.missing === true, `① 无 AGENTS.md:处理器返回 missing(ok=${parsed.ok}, missing=${parsed.missing})`);
|
|
38
|
+
const configs = understandAll([]);
|
|
39
|
+
check(configs.length === 0, "① 无 AGENTS.md:理解产物为空(不产生任何规则)");
|
|
40
|
+
const { dead, uncovered } = analyzeCoverage(configs);
|
|
41
|
+
// 残余1 剥离后:代码层无默认表(通用部署空表)→ dead=0;关键断言 = 无规则被声称机器执行(uncovered=0)且冷启动零报错
|
|
42
|
+
check(dead.size === 0, `① 无 AGENTS.md:dead=0(代码层无默认偏好表)`);
|
|
43
|
+
check(uncovered.length === 0, `① 无 AGENTS.md:零未覆盖(uncovered=0)`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── ② 空白规则文件静默 ──
|
|
47
|
+
{
|
|
48
|
+
writeFileSync(join(sceneDir, "AGENTS.md"), "", "utf8");
|
|
49
|
+
const parsed = loadRules();
|
|
50
|
+
check(parsed.ok === true && parsed.rules.length === 0, `② 空白 AGENTS.md:解析成功且 0 规则(rules=${parsed.rules.length})`);
|
|
51
|
+
const configs = understandAll(parsed.rules);
|
|
52
|
+
check(configs.length === 0, "② 空白 AGENTS.md:理解产物为空");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── ③ 任意编号规则(非本机编号):声明式绑定 + 纯自证分流 ──
|
|
56
|
+
{
|
|
57
|
+
const arbitrary = {
|
|
58
|
+
index: "X-77",
|
|
59
|
+
title: "陌生用户规则(执行等级:A+D)",
|
|
60
|
+
section: "自定义分区",
|
|
61
|
+
level: "A+D",
|
|
62
|
+
body: "- **触发**:使用内联命令。\n- **检查**:拦 node -e / pwsh -c。\n- **动作**:拒绝。\n- **豁免**:无。"
|
|
63
|
+
};
|
|
64
|
+
const plain = understandRule(arbitrary);
|
|
65
|
+
check(plain.handler === "", "③ 任意编号 + 无声明:handler 为空(纯自证分流,不误绑定本机执行器)");
|
|
66
|
+
check(plain.actions.includes("deny") && plain.actions.includes("self-certify"), "③ 任意编号:A+D 动作解析正确(deny+self-certify)");
|
|
67
|
+
const declared = understandRule({ ...arbitrary, body: arbitrary.body + "\n<!-- handler: rule12a-approval -->" });
|
|
68
|
+
check(declared.handler === "rule12a-approval", "③ 任意编号 + 声明:绑定指定执行器");
|
|
69
|
+
const covered = analyzeCoverage(understandAll([arbitrary]));
|
|
70
|
+
check(covered.uncovered.some((u) => u.ruleId === "X-77"), "③ 任意编号 A 级无 handler:被标记未覆盖(提示正确)");
|
|
71
|
+
// 通用化闭环:defaultMap 清空 → 陌生规则无兜底绑定(纯声明式世界,不借道本机偏好表);
|
|
72
|
+
// 注:analyzeCoverage 自省对象恒为本机默认表(体检对象),不受 defaultMap 影响。
|
|
73
|
+
const bare = understandRule(arbitrary, { defaultMap: {} });
|
|
74
|
+
check(bare.handler === "", "③ defaultMap 清空:陌生规则无兜底绑定(纯声明式世界)");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── ④ 四要素 + 执行等级格式契约冒烟(README 格式)──
|
|
78
|
+
{
|
|
79
|
+
const elem = understandRule({
|
|
80
|
+
index: "E-1",
|
|
81
|
+
title: "格式样例(执行等级:B + D)",
|
|
82
|
+
section: "样例分区",
|
|
83
|
+
level: "B + D",
|
|
84
|
+
body: "- **触发**:触发条件。\n- **检查**:检查项。\n- **动作**:动作说明。\n- **豁免**:豁免说明。"
|
|
85
|
+
});
|
|
86
|
+
check(elem.elements.trigger.length > 0 && elem.elements.check.length > 0 && elem.elements.action.length > 0 && elem.elements.exemption.length > 0, "④ 四要素提取完整");
|
|
87
|
+
check(elem.actions.includes("correct") && elem.actions.includes("self-certify"), "④ 组合等级 B + D 动作解析正确");
|
|
88
|
+
check(elem.confidence === "high", "④ 四要素齐全 → 置信 high");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── ⑤ 发布物个人标识扫描(files 白名单;--tgz 指定时扫 tgz 实体)──
|
|
92
|
+
const PERSONAL_RE = /jilian|季涟|D:\\DeepSeek|D:\/DeepSeek|@qq\.com|@163\.com|@outlook\.com|私人注释|个人工作流|本机规则/;
|
|
93
|
+
{
|
|
94
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
95
|
+
const scanFiles = [];
|
|
96
|
+
const collect = (d, prefix = "") => {
|
|
97
|
+
for (const f of readdirSync(d)) {
|
|
98
|
+
const p = join(d, f);
|
|
99
|
+
if (statSync(p).isDirectory()) { if (!["node_modules", ".backups", ".git", ".codegraph", "test"].includes(f)) collect(p, prefix + f + "/"); }
|
|
100
|
+
else if (/\.[a-z]+$/i.test(f) && !/\.(png|jpg|jpeg|gif|webp|svg|tgz|map)$/i.test(f) && !f.startsWith(".")) scanFiles.push(prefix + f);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
collect(join(root, "lib"), "lib/");
|
|
104
|
+
for (const f of ["README.md", "cordis.patch.yml", "package.json"]) {
|
|
105
|
+
if (existsSync(join(root, f))) scanFiles.push(f);
|
|
106
|
+
}
|
|
107
|
+
const hits = [];
|
|
108
|
+
for (const rel of scanFiles) {
|
|
109
|
+
try {
|
|
110
|
+
const text = readFileSync(join(root, rel), "utf8");
|
|
111
|
+
if (PERSONAL_RE.test(text)) hits.push(rel);
|
|
112
|
+
} catch { /* 二进制跳过 */ }
|
|
113
|
+
}
|
|
114
|
+
if (hits.length) {
|
|
115
|
+
// 白名单:README.md 的致谢/仓库信息(官方仓库地址含 jilian-dsh 为必要元数据)、
|
|
116
|
+
// package.json 的 repository 属通用字段 —— 明细列出供人工核对,不直接判红
|
|
117
|
+
const critical = hits.filter((h) => h.startsWith("lib/"));
|
|
118
|
+
check(critical.length === 0, `⑤ 发布物标识扫描:lib 源码命中 ${critical.length} 处(${critical.join(", ")})——个人标识不得进入通用代码`);
|
|
119
|
+
} else {
|
|
120
|
+
check(true, "⑤ 发布物标识扫描:files 白名单无个人标识命中(注意:README 仓库信息属必要元数据,不在此列)");
|
|
121
|
+
}
|
|
122
|
+
// tgz 实体扫描(--tgz):gzip 解包 + 512 字节 tar 头扫描文件内容
|
|
123
|
+
if (tgzArg && existsSync(tgzArg)) {
|
|
124
|
+
const { gunzipSync } = await import("node:zlib");
|
|
125
|
+
const buf = gunzipSync(readFileSync(tgzArg));
|
|
126
|
+
let off = 0;
|
|
127
|
+
const name = new TextDecoder().decode(buf.subarray(off, off + 100)).replace(/\0.*$/, "");
|
|
128
|
+
const sizeStr = new TextDecoder().decode(buf.subarray(off + 124, off + 136)).replace(/\0/g, "").trim();
|
|
129
|
+
off += 512;
|
|
130
|
+
if (name && sizeStr) { /* 首块为目录/文件;逐块扫描见下 */ }
|
|
131
|
+
// 简化:直接全文正则扫描(gzip 已解包,文本可读)
|
|
132
|
+
const text = buf.toString("utf8");
|
|
133
|
+
check(!PERSONAL_RE.test(text), "⑤ tgz 实体扫描:无个人标识命中");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── --deep:完整引擎链冷启动(子进程,需完整权限/CI;沙箱不可用)──
|
|
138
|
+
if (deep) {
|
|
139
|
+
console.log("\n(--deep 模式:请确认在完整权限/CI 环境运行——子进程捕获受沙箱限制)");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
rmSync(sceneDir, { recursive: true, force: true });
|
|
143
|
+
console.log(`\n${FAILS.length === 0 ? "ADEPTITUDE CHECK PASS" : `ADEPTITUDE CHECK FAIL(${FAILS.length} 项)`}`);
|
|
144
|
+
process.exit(FAILS.length === 0 ? 0 : 1);
|
package/scripts/verify-all.mjs
CHANGED
|
@@ -119,6 +119,10 @@ if (judgeReal > 0) {
|
|
|
119
119
|
lines.push("⚠️ 真实判例:近 24h 无 judge-pass/judge-false 记录——裁决器需要一次实弹(非阻塞 WARN,但说明运行证据尚未产生)");
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// ── ⑧ 发布适用性门禁(T6,2026-08-31:通用化"陌生人视角"硬闸)──
|
|
123
|
+
// 无 AGENTS.md 冷启动 / 空白规则静默 / 任意编号规则声明式绑定 / 格式契约冒烟 / 个人标识扫描
|
|
124
|
+
step("发布适用性门禁(无 AGENTS.md 冷启动 / 空白规则 / 任意编号 / 个人标识扫描)", process.execPath, [join(root, "scripts", "publish-aptitude-check.mjs")]);
|
|
125
|
+
|
|
122
126
|
console.log(lines.join("\n"));
|
|
123
127
|
const failed = lines.filter((l) => l.startsWith("❌"));
|
|
124
128
|
if (failed.length) {
|