progmune-runtime 3.7.21 → 3.7.23

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/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.23] — 2026-09-06
4
+
5
+ ### 第三方审计修复批次 + Oracle 隔离政策 + 分资产分级策略
6
+
7
+ - **治理层 fail-open 修复**(Kimi 行号级审计 P0):risk 规则 fail-closed(不再伪造 SSL 输入,真实调用提取,无数据/模块不可用 → 显式违规);kb_coverage 显式化;策略配置解析失败拒绝静默评估(policy CLI exit 2);**execute 写盘策略门**(项目 opt-in `.progmune-policy.json` 后写盘即时验证,BLOCK 自动回滚——策略引擎首次获得写盘强制力);`PROGMUNE_STRICT=false` 逃生门写入审计事件日志;决策层注释与实现对齐(乘积→加权平均);证书时间戳贯通 PolicyContext;**潜伏 bug**:certify 的 plsbRecall 恒为 0(metadata 从未有 recall 字段,HIGH 置信度门槛永远不可达)→ 修为 verified/total
8
+ - **跨命名空间死规则归位 + 活性守卫**(Kimi 发现 3 系统化):全量扫描 14 条死规则——4 条归位(logout_without_invalidate/admin_action_without_check/double_commit/check_resource_ownership 挪进 pre 状态所在命名空间)、10 条 printlab 业务链为文档化例外(待跨命名空间状态引用特性);`protocol-rules-liveness.test.ts` 使死规则永久回归可防
9
+ - **P1 批**:SUPPRESS 等全部决策持久化审计事件日志(含 fpRate/envFactor/importance 全输入);截断序列覆盖率显式降级上报(truncatedSequences + summary 标注);README 双语边界声明(刻意规避不在防护目标内 / BLOCK 强制力三执行点);FAQ 澄清 LLM 密钥为可选增强;落地页免疫叙事收敛
10
+ - **Oracle 隔离政策**(Hunyuan 定律 5 落地,`docs/ORACLE_ISOLATION_POLICY.md`):四类评估体系变更(规则/匹配策略/阈值/别名)必须人工确认,AI 只提案;protocols.json 全量 `status: "confirmed"` + 加载端跳过 proposed + `scripts/rule-propose.js` 提案脚手架;evaluateTrust 输出 **oracleIndependence**(confirmedRules / sameSourceAnnotations / confirmedAliases)
11
+ - **分资产分级策略**(`docs/TIERED_POLICY.md` + `templates/` 三档 + `progmune-init-policy` bin):Tier-1 强制(致命资产,写盘门激活)/ Tier-2 标准(violations 阻断,其余 WARN)/ Tier-3 观察(只报告)——「出错代价 × AI 参与度」二维分级,一条命令落地
12
+ - **审计回应文档** `docs/AUDIT_RESPONSE_2026-09.md`:逐条回应智谱 + Kimi 两份审查(已修复 12 项 / 事实澄清 5 项 / 设计边界 3 项 / 计划 4 项)
13
+ - 发布门:套件全绿(含 policy 新测试 6 项);Python 盲测 64 / TS 795 / C 应用金标零漂移;check 免疫正常
14
+
15
+ ## [3.7.22] — 2026-09-06
16
+
17
+ ### Java 方法学三连——恢复率裁决 + 真实闭环 + 名碰撞消除
18
+
19
+ - **提取器恢复率裁决**(tree-sitter-java AST 基准,spring-realworld):修复三根因(参数注解实参括号 34、通配符泛型 3、构造器 2)+ 调用侧修复(泛型构造调用/注释剔除/多行点链/泛型静态调用)——方法 100%(178/178)、调用边 100%(1216/1216)、0 FP。**词法路线成立,无需 JavaParser 依赖**(与 C/Go 同级别裁决)
20
+ - **真实语料协议行标注闭环**(`REALWORLD_JAVA_ANNOTATION_V1.md`):3 注解 + 1 别名端到端——v1 token 合法流 0 违规 + 摘抽取步反证精确报出;**v2 捕获真实 TP**(spring-realworld `updateUser` 密码明文入库——1581★ 参考实现真实 bug,改密后无法登录;修复变异违规即消);v3 语料无手工资源管理(MyBatis 托管)维持合成金标
21
+ - **接收者限定名匹配(名碰撞根因修复)**:提取器 `className` 捕获(嵌套类感知类名栈)+ 完整点链调用输出;注册层 `Class.method` 限定键(裸名键仅项目内唯一时注册);匹配层大小写不敏感限定精确匹配(带点调用跳过规范化/词段形态);变量名≠类名(`jwtService.` vs `DefaultJwtService`)走项目别名(注解合并后重校验加载)——**1 真实 TP / 0 误报**(9 名碰撞消除)
22
+ - **P4.6 内联深度恢复**:call-sequence 限定调用末段回退解析(同文件优先 + 接收者-类名双向后缀偏好;规则保留集大小写不敏感前置)——违规归因上移到入口(updateProfile),与 TS/Python「helper 违规归因到入口 flow」语义对齐
23
+ - **Spring 现代方言真实语料验证**(`REALWORLD_SPRING_V2.md`,ali-bouali/spring-boot-3-jwt-security,Boot 3 教程标杆):SecurityFilterChain bean + requestMatchers 静态导入 + **String[] 变量白名单展开**(原保守跳过致公开 mutation 被兜底掩盖漏报)+ auth 词段豁免修复(authenticate/refresh-token)——15 路由 0 issues、摘兜底反证 5 重现、V1 语料复扫无回归
24
+ - **audit:realworld fastapi 考核入口修复**(模块名推导 py→fastapi,该框架自工具建立即不可用)+ 全框架语料复扫 14/14 零回归
25
+ - 发布门:套件 255 green;Python 盲测 64 / TS 795 / C 应用金标 F1=95.7% 全部零漂移;check 免疫正常(.progmune_allowlist 存量补录)
26
+
27
+ ## [3.7.15 – 3.7.21] — 2026-09-04/05
28
+
29
+ ### 框架真实语料 12/12 收官 + Java/Spring 语言支持 + Java 协议行引擎化
30
+
31
+ - **框架检测器真实语料全量收官(12/12)**:Fiber 真实生产语料考核(journalist 多层 Register 链传播引擎 12 FP→0 + 反证 9 重现;jiotv_go 能力令牌形态标注)——8 启发式 + 4 结构级全部真实语料验证到 0 协议级 FP + 敏感性反证;CLAUDE.md 转正待办清空
32
+ - **audit:realworld 一键考核工具 + 证据档位制**:任意真实开源项目 clone/vendor → 扫描 → JSON 报告 → 金标标注模板(docs/REALWORLD_METHODOLOGY.md)
33
+ - **Java/Spring 语言支持(3.7.17-18)**:`extract-ir-java.ts` 提取器(纯 TS 词法)+ Spring 路由覆盖模型(19 路由/12 mutation 全解析、0 issues、anyRequest 兜底翻转反证 10 重现)+ 调用图提取 + SSG 方向决策(`REALWORLD_SPRING_V1.md`)
34
+ - **Java 协议行引擎化(3.7.19-21)**:协议行金标 v1 + Spring 方言扩展(requestMatchers/类级 @PreAuthorize);token 生命周期 / auth-register 哈希先行 / resource 管理(invalidate + 泄漏端)三族引擎回归锁定(docs/java-language-status.md)
35
+
3
36
  ## [3.7.14] — 2026-09-02
4
37
 
5
38
  ### Go 提取器修复 + 恢复率裁决 + 框架诚实分层 + 三语料实验
package/README.md CHANGED
@@ -118,6 +118,16 @@ Progmune is honest about what it can and cannot verify.
118
118
  - **Runtime behavior** — Progmune is static analysis only; no DAST/sandbox execution.
119
119
  - **Framework internals** — well-known framework dispatch/cache machinery (e.g. django-unicorn internals) can produce a small number of boundary false positives; they are documented per-corpus in the benchmark gold files.
120
120
  - **Known failure boundaries are documented** rather than hidden: if Progmune cannot verify a language (e.g. Go), Confidence is lowered instead of pretending 100%.
121
+ - **Deliberate evasion is out of scope** — the annotation-driven model catches *accidental* AI protocol mistakes (missing prerequisites, wrong ordering); it does not defend against adversarial renaming/obfuscation (true of any static tool).
122
+ - **BLOCK enforcement is the integrator's responsibility** — Progmune outputs decisions and evidence; it does not intercept at runtime. OS-level enforcement points: `trust --ci` exit code (CI gates), `policy CLI` exit code, and the opt-in write-gate (with `.progmune-policy.json`, `execute` verifies immediately after writing and rolls back on BLOCK).
123
+
124
+ **Tiered adoption by asset class** ([TIERED_POLICY](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/TIERED_POLICY.md)): don't gate all code alike — pick a tier by "cost of failure × AI involvement", one command to install:
125
+
126
+ ```bash
127
+ npx progmune-init-policy --tier 1 # Enforce: auth/payment/resource lifecycle — write-gate active
128
+ npx progmune-init-policy --tier 2 # Standard: business logic — violations block, rest WARN
129
+ npx progmune-init-policy --tier 3 # Observe: tools/demos — report only
130
+ ```
121
131
 
122
132
  → [Full Coverage Matrix](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/coverage-matrix-en.md)
123
133
 
package/README.zh-CN.md CHANGED
@@ -118,6 +118,16 @@ Progmune 对能验证什么、不能验证什么保持诚实。
118
118
  - **运行时行为**——Progmune 仅做静态分析;无 DAST/沙箱执行。
119
119
  - **框架内部件**——知名框架的分发/缓存机制(如 django-unicorn 内部件)可能产生少量边界误报;各语料基准 gold 文件中已逐条记录。
120
120
  - **已知失败边界一律记录**而非隐藏:如果 Progmune 无法验证某语言(如 Go),置信度会降低而不是假装 100%。
121
+ - **刻意规避不在防护目标内**——注解驱动模型拦截的是 AI 的「意外」协议错误(漏写前置步骤、顺序颠倒);对故意改名/混淆的对抗性规避不设防(任何静态工具同理)。
122
+ - **BLOCK 的强制力由集成方承担**——Progmune 输出判定与证据,不运行时拦截。OS 级强制执行点:`trust --ci` 退出码(CI 门禁)、`policy CLI` 退出码、以及 opt-in 的写盘策略门(项目配置 `.progmune-policy.json` 后,execute 写盘即时验证,BLOCK 自动回滚)。
123
+
124
+ **分资产分级接入**([TIERED_POLICY](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/TIERED_POLICY.md)):不要给所有代码同一套门禁——按「出错代价 × AI 参与度」选档,一条命令落地:
125
+
126
+ ```bash
127
+ npx progmune-init-policy --tier 1 # 强制:鉴权/支付/资源生命周期——写盘策略门激活
128
+ npx progmune-init-policy --tier 2 # 标准:业务逻辑——violations 阻断,其余 WARN
129
+ npx progmune-init-policy --tier 3 # 观察:工具/demo——只报告不拦截
130
+ ```
121
131
 
122
132
  → [完整覆盖矩阵](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/coverage-matrix.md)
123
133
 
@@ -68,6 +68,7 @@ function buildCallSequences(ir, keepNames, maxCalls = exports.MAX_SEQUENCE_CALLS
68
68
  // 跨文件函数指针分发仍不可见(L3 结论不变)。
69
69
  const fnMap = new Map();
70
70
  const fnMapByFile = new Map();
71
+ const fnMapAll = new Map(); // 裸名 → 全部定义(限定回退候选)
71
72
  for (const f of ir) {
72
73
  if (!isProjectFn(f))
73
74
  continue;
@@ -79,9 +80,55 @@ function buildCallSequences(ir, keepNames, maxCalls = exports.MAX_SEQUENCE_CALLS
79
80
  }
80
81
  if (!byFile.has(f.name))
81
82
  byFile.set(f.name, f);
83
+ const all = fnMapAll.get(f.name) || [];
84
+ all.push(f);
85
+ fnMapAll.set(f.name, all);
82
86
  }
83
- /** 调用解析:同文件定义优先,全局按名回退(跨文件同名时每个文件绑自己的) */
84
- const resolveCall = (fromFile, name) => fnMapByFile.get(fromFile)?.get(name) ?? fnMap.get(name);
87
+ // 保留集小写副本:限定调用名大小写不敏感命中(Java 规则键
88
+ // User.update vs 变量调用 user.update——大小写差异下的保留判定)
89
+ const keepNamesLower = keepNames ? new Set([...keepNames].map((k) => k.toLowerCase())) : undefined;
90
+ /** 限定名末段回退(Java 接收者限定输出):同文件优先;全局候选
91
+ * 偏好 className 与接收者变量名大小写不敏感后缀匹配(user→User、
92
+ * userRepository→MyBatisUserRepository、jwtService→DefaultJwtService
93
+ * ——Java 字段名=类名 camelCase 的惯例);无偏好时 last-wins 兜底 */
94
+ const resolveQualified = (fromFile, name) => {
95
+ const dot = name.lastIndexOf(".");
96
+ const seg = name.slice(dot + 1);
97
+ if (!seg)
98
+ return undefined;
99
+ const byFile = fnMapByFile.get(fromFile)?.get(seg);
100
+ if (byFile)
101
+ return byFile;
102
+ const all = fnMapAll.get(seg) || [];
103
+ if (all.length === 0)
104
+ return undefined;
105
+ if (all.length === 1)
106
+ return all[0];
107
+ const recv = name.slice(0, dot).toLowerCase();
108
+ // 双向后缀:变量名=类名去前缀(userRepository→MyBatisUserRepository、
109
+ // jwtService→DefaultJwtService——类名以接收者结尾)或变量名带类名
110
+ // (类名以接收者结尾的场景罕见,保留)
111
+ const hit = recv
112
+ ? all.find((f) => {
113
+ if (!f.className)
114
+ return false;
115
+ const cn = f.className.toLowerCase();
116
+ return recv === cn || cn.endsWith(recv) || recv.endsWith(cn);
117
+ })
118
+ : undefined;
119
+ return hit ?? fnMap.get(seg);
120
+ };
121
+ /** 调用解析:同文件定义优先,全局按名回退(跨文件同名时每个文件绑自己的);
122
+ * 限定名精确不中时末段回退——无注解项目 helper 的 P4.6 内联深度恢复
123
+ * (Java 限定化前此类调用按裸名解析,限定化后曾退化为外部 token) */
124
+ const resolveCall = (fromFile, name) => {
125
+ const exact = fnMapByFile.get(fromFile)?.get(name) ?? fnMap.get(name);
126
+ if (exact)
127
+ return exact;
128
+ if (!name.includes("."))
129
+ return undefined;
130
+ return resolveQualified(fromFile, name);
131
+ };
85
132
  // 被项目函数调用过的函数不是入口(其片段并入调用方展开序列)——
86
133
  // 按 文件+名字 粒度判定,避免 A 文件的 static x 被 B 文件的调用误判非入口
87
134
  const fnKey = (file, name) => file + "::" + name;
@@ -119,9 +166,16 @@ function buildCallSequences(ir, keepNames, maxCalls = exports.MAX_SEQUENCE_CALLS
119
166
  const expandCall = (name, depth, visiting, fromFile) => {
120
167
  if (depth > MAX_DEPTH || visiting.has(name))
121
168
  return [];
169
+ // 规则函数保留优先(先于解析):限定调用名大小写不敏感命中保留集
170
+ // → 保留 token 不解析不内联(解析会经末段回退内联掉规则 token,
171
+ // 且可能绑到同名异类定义)
172
+ if (keepNames && (keepNames.has(name) || (name.includes(".") && keepNamesLower.has(name.toLowerCase())))) {
173
+ budget.left--;
174
+ return [name];
175
+ }
122
176
  const fn = resolveCall(fromFile, name);
123
- // 外部调用或规则函数:调用名保留给匹配层,不内联(记 1 个调用)
124
- if (!fn || (keepNames && keepNames.has(name))) {
177
+ // 外部调用:调用名保留给匹配层,不内联(记 1 个调用)
178
+ if (!fn) {
125
179
  budget.left--;
126
180
  return [name];
127
181
  }
@@ -137,3 +137,58 @@ function fn(name, file, calls) {
137
137
  (0, vitest_1.expect)((0, call_sequence_1.collectProjectFunctionNames)([external]).size).toBe(0);
138
138
  });
139
139
  });
140
+ // ── 限定调用末段回退解析(Java 接收者限定输出,2026-09-06)──
141
+ /** 带 className 的最小 FunctionInfo(Java 接收者限定语义) */
142
+ function fnQ(name, file, calls, className) {
143
+ return { name, params: [], returnType: "void", file, calls, exported: true, external: false, className };
144
+ }
145
+ (0, vitest_1.describe)("buildCallSequences(限定调用回退解析)", () => {
146
+ (0, vitest_1.it)("限定调用末段回退:无注解项目 helper 经接收者解析后被内联(P4.6 深度恢复)", () => {
147
+ const ir = [
148
+ fnQ("save", "MyBatisUserRepository.java", ["insertUser"], "MyBatisUserRepository"),
149
+ fnQ("insertUser", "MyBatisUserRepository.java", ["db.execute"]),
150
+ fnQ("updateUser", "UserService.java", ["userRepository.save"]),
151
+ ];
152
+ const seqs = (0, call_sequence_1.buildCallSequences)(ir);
153
+ (0, vitest_1.expect)(seqs).toHaveLength(1);
154
+ (0, vitest_1.expect)(seqs[0].function).toBe("updateUser");
155
+ // save 被解析并内联 → insertUser(叶子)保留
156
+ (0, vitest_1.expect)(seqs[0].calls).toEqual(["insertUser"]);
157
+ });
158
+ (0, vitest_1.it)("限定调用命中保留集(大小写不敏感):规则 token 不解析不内联", () => {
159
+ const ir = [
160
+ fnQ("update", "User.java", ["audit"], "User"),
161
+ fnQ("audit", "User.java", ["db.write"]),
162
+ fnQ("updateUser", "UserService.java", ["user.update"]),
163
+ ];
164
+ // 规则键 = 类限定(User.update);调用 = 变量限定(user.update)——大小写差异
165
+ const seqs = (0, call_sequence_1.buildCallSequences)(ir, new Set(["User.update"]));
166
+ (0, vitest_1.expect)(seqs).toHaveLength(1);
167
+ (0, vitest_1.expect)(seqs[0].function).toBe("updateUser");
168
+ // 保留 token 而非内联(audit 不得出现)
169
+ (0, vitest_1.expect)(seqs[0].calls).toEqual(["user.update"]);
170
+ });
171
+ (0, vitest_1.it)("接收者-类名后缀偏好:同名 save 绑定到接收者对应类(userRepository → MyBatisUserRepository)", () => {
172
+ const ir = [
173
+ fnQ("save", "MyBatisUserRepository.java", ["insertUser"], "MyBatisUserRepository"),
174
+ fnQ("save", "MyBatisArticleRepository.java", ["insertArticle"], "MyBatisArticleRepository"),
175
+ fnQ("insertUser", "MyBatisUserRepository.java", ["db.execute"]),
176
+ fnQ("insertArticle", "MyBatisArticleRepository.java", ["db.execute"]),
177
+ fnQ("updateUser", "UserService.java", ["userRepository.save"]),
178
+ fnQ("updateArticle", "ArticleCommandService.java", ["articleRepository.save"]),
179
+ ];
180
+ const seqs = (0, call_sequence_1.buildCallSequences)(ir);
181
+ const byFn = new Map(seqs.map((s) => [s.function, s.calls]));
182
+ (0, vitest_1.expect)(byFn.get("updateUser")).toEqual(["insertUser"]);
183
+ (0, vitest_1.expect)(byFn.get("updateArticle")).toEqual(["insertArticle"]);
184
+ });
185
+ (0, vitest_1.it)("入口判定:仅被限定名调用的函数不是入口", () => {
186
+ const ir = [
187
+ fnQ("save", "MyBatisUserRepository.java", ["db.execute"]),
188
+ fnQ("updateUser", "UserService.java", ["userRepository.save"]),
189
+ ];
190
+ const seqs = (0, call_sequence_1.buildCallSequences)(ir);
191
+ (0, vitest_1.expect)(seqs.filter((s) => s.function === "save")).toHaveLength(0);
192
+ (0, vitest_1.expect)(seqs).toHaveLength(1);
193
+ });
194
+ });
package/dist/certify.js CHANGED
@@ -49,6 +49,14 @@ exports.certify = certify;
49
49
  exports.formatCertificate = formatCertificate;
50
50
  const fs = __importStar(require("fs"));
51
51
  const path = __importStar(require("path"));
52
+ // 静态导入(vitest 下 lazy require CJS 互操作不可靠——engine.ts 同款
53
+ // 陷阱,2026-08-27 DSH 修复先例;以下模块均不反向依赖 certify,无环)
54
+ const failure_corpus_1 = require("./failure-corpus");
55
+ const ssg_validator_1 = require("./ssg-validator");
56
+ const protocol_registry_1 = require("./protocol-registry");
57
+ const ledger_registry_1 = require("./ledger-registry");
58
+ const plsb_benchmark_1 = require("./plsb-benchmark");
59
+ const protocol_knowledge_1 = require("./protocol-knowledge");
52
60
  // ── Core ──
53
61
  function certify(filePath) {
54
62
  const absPath = path.resolve(filePath);
@@ -66,8 +74,7 @@ function certify(filePath) {
66
74
  const sessionId = markerMatch[1];
67
75
  const markerTimestamp = markerMatch[2];
68
76
  // 2. Load session from corpus
69
- const { getAllSessions } = require("./failure-corpus");
70
- const sessions = getAllSessions();
77
+ const sessions = (0, failure_corpus_1.getAllSessions)();
71
78
  const session = sessions.find((s) => s.sessionId === sessionId || s.sessionId?.startsWith(sessionId));
72
79
  // 3. Collect transitions
73
80
  let allTransitions = [];
@@ -85,9 +92,7 @@ function certify(filePath) {
85
92
  let violations = 0;
86
93
  try {
87
94
  if (session && allTransitions.length > 0) {
88
- const { checkLedgerConsistency } = require("./ssg-validator");
89
- const { getNsInit } = require("./protocol-registry");
90
- const result = checkLedgerConsistency(allTransitions, getNsInit());
95
+ const result = (0, ssg_validator_1.checkLedgerConsistency)(allTransitions, (0, protocol_registry_1.getNsInit)());
91
96
  validated = result.consistent;
92
97
  violations = (result.violations || []).length;
93
98
  }
@@ -102,12 +107,10 @@ function certify(filePath) {
102
107
  let fingerprint = "";
103
108
  let provenanceIntact = true;
104
109
  try {
105
- const { verifyFingerprint } = require("./ledger-registry");
106
- const fp = verifyFingerprint(sessionId, allTransitions);
110
+ const fp = (0, ledger_registry_1.verifyFingerprint)(sessionId, allTransitions);
107
111
  fingerprint = fp.stored?.ledgerHash || fp.stored?.ledgerHash || "";
108
112
  if (!fingerprint) {
109
- const { getFingerprint } = require("./ledger-registry");
110
- const fp2 = getFingerprint(sessionId);
113
+ const fp2 = (0, ledger_registry_1.getFingerprint)(sessionId);
111
114
  fingerprint = fp2?.ledgerHash || "";
112
115
  }
113
116
  provenanceIntact = !fp.tampered;
@@ -120,11 +123,14 @@ function certify(filePath) {
120
123
  let plsbCoverage = "unknown";
121
124
  let plsbRecall = 0;
122
125
  try {
123
- const { buildPLSB, PROTOCOL_WEAKNESS_TAXONOMY } = require("./plsb-benchmark");
124
- const benchmark = buildPLSB();
126
+ const benchmark = (0, plsb_benchmark_1.buildPLSB)();
125
127
  plsbVersion = benchmark.version || "1.0";
126
- plsbRecall = benchmark.metadata?.recall || 0;
127
- const taxonomy = PROTOCOL_WEAKNESS_TAXONOMY;
128
+ // recall = verified/total(类型修复暴露的潜伏 bug:metadata 从未有
129
+ // recall 字段,原写法恒为 0 → HIGH 置信度门槛永远不可达)
130
+ plsbRecall = benchmark.metadata
131
+ ? benchmark.metadata.verified / Math.max(1, benchmark.metadata.total)
132
+ : 0;
133
+ const taxonomy = plsb_benchmark_1.PROTOCOL_WEAKNESS_TAXONOMY;
128
134
  const byPLS = benchmark.metadata?.byPLS || {};
129
135
  const covered = taxonomy.filter((t) => (byPLS[t.id] || 0) > 0).length;
130
136
  plsbCoverage = `${covered}/${taxonomy.length}`;
@@ -161,8 +167,7 @@ function certify(filePath) {
161
167
  let kbVersion;
162
168
  let kbAssets;
163
169
  try {
164
- const { buildKnowledgeBase } = require("./protocol-knowledge");
165
- const kb = buildKnowledgeBase();
170
+ const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
166
171
  kbVersion = kb.version;
167
172
  const stableAssets = kb.units.filter((u) => u.maturity === "stable");
168
173
  kbAssets = stableAssets.map((u) => ({
package/dist/check.js CHANGED
@@ -473,6 +473,24 @@ step("4/6 Ledger 不变量");
473
473
  fail("PROGMUNE_STRICT=true — 严格不变量断言发现违规");
474
474
  }
475
475
  }
476
+ else if (process.env.PROGMUNE_STRICT === "false") {
477
+ // 逃生门记账(审计修复 2026-09-06):降级必须留痕——
478
+ // 不变量违规降级为提示时,向审计事件日志追加一条记录
479
+ warn("PROGMUNE_STRICT=false — 不变量违规降级为提示(已记入审计事件日志)");
480
+ try {
481
+ const fs = require("fs");
482
+ const path = require("path");
483
+ const auditDir = path.join(process.cwd(), ".progmune_memory");
484
+ if (!fs.existsSync(auditDir))
485
+ fs.mkdirSync(auditDir, { recursive: true });
486
+ fs.appendFileSync(path.join(auditDir, "audit-events.jsonl"), JSON.stringify({
487
+ event: "PROGMUNE_STRICT_DISABLED",
488
+ timestamp: new Date().toISOString(),
489
+ cwd: process.cwd(),
490
+ }) + "\n", "utf-8");
491
+ }
492
+ catch { /* audit log write — best-effort */ }
493
+ }
476
494
  // P0: Fingerprint registration — ensure every session has an execution certificate
477
495
  {
478
496
  const registered = (0, ledger_registry_1.registerAllMissingFingerprints)();
@@ -17,7 +17,9 @@
17
17
  * Output: BLOCK/WARN/ALLOW | promote/demote/hold | fix/retry/rollback | accept/reject
18
18
  *
19
19
  * Importance is COMPUTED, not configured:
20
- * Importance = Impact × Universality × Criticality
20
+ * Importance = 0.4×Impact + 0.25×Universality + 0.35×Criticality
21
+ * (加权平均,权重之和为 1;Impact 权重最高——安全影响优先于
22
+ * 出现广度。审计修复 2026-09-06:原注释写乘积公式,与实现矛盾)
21
23
  */
22
24
  Object.defineProperty(exports, "__esModule", { value: true });
23
25
  exports.DecisionEngine = void 0;
@@ -105,7 +107,7 @@ function computeImportance(asset) {
105
107
  const impact = computeImpact(asset);
106
108
  const universality = computeUniversality(asset);
107
109
  const criticality = computeCriticality(asset);
108
- // Weighted product: Impact is most important, Universality is secondary
110
+ // Weighted average (weights sum to 1): Impact is most important, Universality secondary
109
111
  return Math.round((impact * 0.4 + universality * 0.25 + criticality * 0.35) * 100);
110
112
  }
111
113
  /**
@@ -181,7 +183,9 @@ class DecisionEngine {
181
183
  reason: action === "BLOCK"
182
184
  ? `High-confidence violation (${(confidence * 100).toFixed(0)}%, importance ${importance})`
183
185
  : action === "SUPPRESS"
184
- ? `Low confidence (${(confidence * 100).toFixed(0)}%), ${context.recentFPs || 0} recent FPs`
186
+ // 审计修复 2026-09-06:抑制决策的输入全部入审计记录
187
+ // (FP 率/环境因子/资产),可追溯为何被压、被谁压
188
+ ? `Suppressed (silent) at ${(confidence * 100).toFixed(0)}% confidence — fpRate=${(fpRate * 100).toFixed(0)}%, envFactor=${envFactor}, importance=${importance}, ${context.recentFPs || 0} recent FPs`
185
189
  : `Moderate confidence (${(confidence * 100).toFixed(0)}%) — ${action}`,
186
190
  provenance: {
187
191
  assets: [asset.id],
@@ -373,6 +377,25 @@ class DecisionEngine {
373
377
  const id = `DEC-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
374
378
  const decision = { id, ...partial };
375
379
  this.history.push(decision);
380
+ // 审计修复 2026-09-06:决策(尤其 SUPPRESS 静默抑制)持久化到审计
381
+ // 事件日志——此前仅内存记录,抑制决策对外不可见、不可追溯
382
+ try {
383
+ const fs = require("fs");
384
+ const path = require("path");
385
+ const auditDir = path.join(process.cwd(), ".progmune_memory");
386
+ if (!fs.existsSync(auditDir))
387
+ fs.mkdirSync(auditDir, { recursive: true });
388
+ fs.appendFileSync(path.join(auditDir, "audit-events.jsonl"), JSON.stringify({
389
+ event: "DECISION_RECORDED",
390
+ decisionId: id,
391
+ kind: decision.kind,
392
+ action: decision.action,
393
+ confidence: decision.confidence,
394
+ reason: decision.reason,
395
+ timestamp: decision.provenance.timestamp,
396
+ }) + "\n", "utf-8");
397
+ }
398
+ catch { /* audit log write — best-effort */ }
376
399
  return decision;
377
400
  }
378
401
  }
package/dist/execute.js CHANGED
@@ -43,6 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  Object.defineProperty(exports, "__esModule", { value: true });
44
44
  exports.recordGeneration = recordGeneration;
45
45
  exports.getExecutionMetrics = getExecutionMetrics;
46
+ exports.applyPolicyGateAfterWrite = applyPolicyGateAfterWrite;
46
47
  exports.execute = execute;
47
48
  exports.verifyCompiles = verifyCompiles;
48
49
  exports.verifyFileMarker = verifyFileMarker;
@@ -52,6 +53,9 @@ const planner_1 = require("./planner");
52
53
  const extract_project_ir_1 = require("./extract-project-ir");
53
54
  const failure_collector_1 = require("./failure-collector");
54
55
  const emitter_1 = require("./emitter");
56
+ // 静态导入(vitest 下 lazy require CJS 互操作不可靠;certify/policy 无环)
57
+ const certify_1 = require("./certify");
58
+ const engine_1 = require("./policy/engine");
55
59
  const METRICS_FILE = ".progmune_corpus/metrics.json";
56
60
  function loadMetrics() {
57
61
  try {
@@ -95,6 +99,68 @@ function getExecutionMetrics() {
95
99
  * @param projectPath - Absolute path to project root
96
100
  * @param filePath - Optional: write generated code to this file
97
101
  */
102
+ /**
103
+ * 策略门(审计修复 2026-09-06):项目配置 .progmune-policy.json 时,
104
+ * 写盘后即时 certify + evaluatePolicy;BLOCK 或验证自身失败 → 回滚写盘
105
+ * (恢复 prevContent 或删除新文件)——策略引擎 BLOCK 至此获得写盘强制力。
106
+ * 未配置策略文件时无操作(旧行为保持)。
107
+ */
108
+ function applyPolicyGateAfterWrite(projectPath, resolvedPath, prevContent) {
109
+ const policyOptIn = fs.existsSync(path.join(projectPath, ".progmune-policy.json"));
110
+ if (!policyOptIn)
111
+ return { blocked: false };
112
+ const rollback = () => {
113
+ if (prevContent !== undefined) {
114
+ fs.writeFileSync(resolvedPath, prevContent, "utf-8");
115
+ }
116
+ else if (fs.existsSync(resolvedPath)) {
117
+ fs.unlinkSync(resolvedPath);
118
+ }
119
+ };
120
+ try {
121
+ const cert = (0, certify_1.certify)(resolvedPath);
122
+ const { rules, configError } = (0, engine_1.loadPolicyConfig)(projectPath);
123
+ if (configError) {
124
+ throw new Error(configError);
125
+ }
126
+ const policyResult = (0, engine_1.evaluatePolicy)({
127
+ certificate: {
128
+ validated: cert.validated,
129
+ confidence: cert.confidence,
130
+ provenanceIntact: cert.provenanceIntact,
131
+ fingerprint: cert.fingerprint,
132
+ violations: cert.violations,
133
+ plsbCoverage: cert.plsbCoverage,
134
+ plsbRecall: cert.plsbRecall,
135
+ degraded: cert.degraded,
136
+ sessionId: cert.sessionId,
137
+ file: cert.file,
138
+ timestamp: cert.timestamp,
139
+ },
140
+ }, rules);
141
+ if (!policyResult.passed) {
142
+ rollback();
143
+ return {
144
+ blocked: true,
145
+ decision: policyResult.verdict,
146
+ violations: policyResult.violations.map((v) => ({
147
+ ruleType: v.rule.type,
148
+ detail: v.detail || v.actual,
149
+ })),
150
+ error: `Policy gate BLOCKED the write: ${policyResult.violations.map((v) => v.rule.type).join(", ")} — file rolled back`,
151
+ };
152
+ }
153
+ return { blocked: false };
154
+ }
155
+ catch (e) {
156
+ // fail-closed:策略验证自身失败 → 回滚写盘
157
+ rollback();
158
+ return {
159
+ blocked: true,
160
+ error: `Policy gate failed (fail-closed): ${e.message} — file rolled back`,
161
+ };
162
+ }
163
+ }
98
164
  /** @requires INTENT @produces CODE */
99
165
  /** @requires INTENT @produces CODE */
100
166
  async function execute(intent, projectPath, filePath) {
@@ -149,14 +215,31 @@ async function execute(intent, projectPath, filePath) {
149
215
  irFunctionCount: ir.length,
150
216
  protocolRuleCount,
151
217
  });
152
- // 5. Write to file if requested
218
+ // 5. Write to file if requested(策略门:项目配置了 .progmune-policy.json
219
+ // 时,写盘后即时验证;BLOCK → 回滚写盘并返回策略违规——审计修复
220
+ // 2026-09-06:此前策略引擎与代码生成管道完全脱节,BLOCK 无强制力)
153
221
  if (filePath) {
154
222
  const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectPath, filePath);
155
223
  const dir = path.dirname(resolvedPath);
156
224
  if (!fs.existsSync(dir)) {
157
225
  fs.mkdirSync(dir, { recursive: true });
158
226
  }
227
+ const prevContent = fs.existsSync(resolvedPath)
228
+ ? fs.readFileSync(resolvedPath, "utf-8") : undefined;
159
229
  fs.writeFileSync(resolvedPath, code, "utf-8");
230
+ const gate = applyPolicyGateAfterWrite(projectPath, resolvedPath, prevContent);
231
+ if (gate.blocked) {
232
+ return {
233
+ success: false, degraded: planResult.degraded ?? false, code,
234
+ sessionId: planResult.sessionId, hash: "", ruleHash: planResult.ruleHash || "",
235
+ irFunctionCount: ir.length, protocolRuleCount, violations: 0,
236
+ repairApplied: false, repairCount: 0, repairBranchIds: [],
237
+ branchWinner: undefined,
238
+ policyDecision: gate.decision,
239
+ policyViolations: gate.violations,
240
+ error: gate.error,
241
+ };
242
+ }
160
243
  }
161
244
  // 6. Compute hash (from code content)
162
245
  const crypto = require("crypto");