progmune-runtime 3.7.22 → 3.7.24

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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.24] — 2026-09-06
4
+
5
+ ### 审计复检三项修复
6
+
7
+ - **空规则集 fail-closed**:`evaluatePolicy` 对空规则数组直接 BLOCK(合成 `policy_config` 违规)——空数组是 truthy,原实现零规则→零违规→ALLOW;写盘门 / policy CLI / MCP 三路径同步收口,测试锁定
8
+ - **loadEnterprisePolicyConfig 解析失败显式携带 configError**(与 loadPolicyConfig 同口径,六条 fail-open 中第 4 条全修)
9
+ - **写盘门口径修正**(README 双语 / TIERED_POLICY / AUDIT_RESPONSE):「写盘策略门」→「**写后回滚门——BLOCK 时文件不残留**」,明确为补偿控制而非写前拦截,写盘与回滚之间进程中断的残留为已知边界
10
+ - **落地页 66 金标口径自解释**:「66 金标 检出 64(97%)/ 100% / 0 FP——2 条为注解依赖前置的已知漏检(基线文档单列)」,与发布门 64 一致
11
+ - npm gitHead 溯源核对:npm gitHead = tag v3.7.23 commit,三点对齐(发布产物 ↔ tag ↔ 发布 commit)
12
+ - 回归 29 green;check 免疫正常
13
+
14
+ ## [3.7.23] — 2026-09-06
15
+
16
+ ### 第三方审计修复批次 + Oracle 隔离政策 + 分资产分级策略
17
+
18
+ - **治理层 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
19
+ - **跨命名空间死规则归位 + 活性守卫**(Kimi 发现 3 系统化):全量扫描 14 条死规则——4 条归位(logout_without_invalidate/admin_action_without_check/double_commit/check_resource_ownership 挪进 pre 状态所在命名空间)、10 条 printlab 业务链为文档化例外(待跨命名空间状态引用特性);`protocol-rules-liveness.test.ts` 使死规则永久回归可防
20
+ - **P1 批**:SUPPRESS 等全部决策持久化审计事件日志(含 fpRate/envFactor/importance 全输入);截断序列覆盖率显式降级上报(truncatedSequences + summary 标注);README 双语边界声明(刻意规避不在防护目标内 / BLOCK 强制力三执行点);FAQ 澄清 LLM 密钥为可选增强;落地页免疫叙事收敛
21
+ - **Oracle 隔离政策**(Hunyuan 定律 5 落地,`docs/ORACLE_ISOLATION_POLICY.md`):四类评估体系变更(规则/匹配策略/阈值/别名)必须人工确认,AI 只提案;protocols.json 全量 `status: "confirmed"` + 加载端跳过 proposed + `scripts/rule-propose.js` 提案脚手架;evaluateTrust 输出 **oracleIndependence**(confirmedRules / sameSourceAnnotations / confirmedAliases)
22
+ - **分资产分级策略**(`docs/TIERED_POLICY.md` + `templates/` 三档 + `progmune-init-policy` bin):Tier-1 强制(致命资产,写盘门激活)/ Tier-2 标准(violations 阻断,其余 WARN)/ Tier-3 观察(只报告)——「出错代价 × AI 参与度」二维分级,一条命令落地
23
+ - **审计回应文档** `docs/AUDIT_RESPONSE_2026-09.md`:逐条回应智谱 + Kimi 两份审查(已修复 12 项 / 事实澄清 5 项 / 设计边界 3 项 / 计划 4 项)
24
+ - 发布门:套件全绿(含 policy 新测试 6 项);Python 盲测 64 / TS 795 / C 应用金标零漂移;check 免疫正常
25
+
3
26
  ## [3.7.22] — 2026-09-06
4
27
 
5
28
  ### Java 方法学三连——恢复率裁决 + 真实闭环 + 名碰撞消除
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-then-rollback gate** (with `.progmune-policy.json`, `execute` writes then verifies immediately; on BLOCK the file does not persist. This is a compensating control, not pre-write interception: a process killed between write and rollback may leave the content behind — a known boundary).
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
 
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");
@@ -1090,6 +1090,7 @@ ${result.code.split("\n").slice(0, 80).join("\n")}` }] };
1090
1090
  degraded: cert.degraded,
1091
1091
  sessionId: cert.sessionId,
1092
1092
  file: cert.file,
1093
+ timestamp: cert.timestamp,
1093
1094
  },
1094
1095
  accountability: acct ? {
1095
1096
  humanEvents: acct.humanEvents,
@@ -69,7 +69,13 @@ catch {
69
69
  }
70
70
  // 3. Load policy (from project config or defaults)
71
71
  const projectDir = process.env.PROGMUNE_PROJECT_DIR || process.cwd();
72
- const { rules, source } = (0, engine_1.loadPolicyConfig)(projectDir, policyPath);
72
+ const { rules, source, configError } = (0, engine_1.loadPolicyConfig)(projectDir, policyPath);
73
+ // 配置解析失败:拒绝静默评估(fail-closed 信号,审计修复 2026-09-06)
74
+ if (configError) {
75
+ console.error(`❌ ${configError}`);
76
+ console.error(` 拒绝在损坏的策略配置下评估——请修复配置后重试。`);
77
+ process.exit(2);
78
+ }
73
79
  // 4. Evaluate policy
74
80
  const ctx = {
75
81
  certificate: {
@@ -83,6 +89,7 @@ const ctx = {
83
89
  degraded: cert.degraded,
84
90
  sessionId: cert.sessionId,
85
91
  file: cert.file,
92
+ timestamp: cert.timestamp,
86
93
  },
87
94
  accountability: acct ? {
88
95
  humanEvents: acct.humanEvents,
@@ -46,6 +46,10 @@ exports.loadEnterprisePolicyConfig = loadEnterprisePolicyConfig;
46
46
  const fs = __importStar(require("fs"));
47
47
  const path = __importStar(require("path"));
48
48
  const types_1 = require("./types");
49
+ // 静态导入(vitest 环境下 lazy require 的 CJS 互操作不可靠——engine.ts
50
+ // 同款陷阱,2026-08-27 DSH 修复先例;两模块无环,静态导入安全)
51
+ const risk_model_1 = require("../risk-model");
52
+ const protocol_knowledge_1 = require("../protocol-knowledge");
49
53
  function confidenceToNumber(c) {
50
54
  if (c === "high")
51
55
  return 2;
@@ -93,13 +97,66 @@ function loadPolicyConfig(projectPath, configPath) {
93
97
  return { rules: merged, source: cfgFile };
94
98
  }
95
99
  catch (e) {
96
- console.error(`⚠️ Failed to load policy config: ${e.message}. Using defaults.`);
97
- return { rules: [...types_1.DEFAULT_POLICY], source: "built-in defaults (config error)" };
100
+ // 审计修复(fail-closed 信号):解析失败不再静默回退——显式携带
101
+ // configError,由调用方(policy CLI / MCP)决定拒绝评估并报错
102
+ console.error(`⚠️ Failed to load policy config: ${e.message}.`);
103
+ return {
104
+ rules: [...types_1.DEFAULT_POLICY],
105
+ source: "built-in defaults (config error)",
106
+ configError: `Failed to parse ${cfgFile}: ${e.message}`,
107
+ };
108
+ }
109
+ }
110
+ /** 真实调用提取(certificate.file 的 best-effort 词法扫描,供 risk 规则
111
+ * 使用——不伪造输入;读不到文件时返回空数组,由调用方按 fail-closed
112
+ * 计为违规) */
113
+ function extractCallsBestEffort(file) {
114
+ if (!file)
115
+ return [];
116
+ try {
117
+ const fs = require("fs");
118
+ const code = fs.readFileSync(file, "utf-8");
119
+ const calls = [];
120
+ const re = /(?<=^|[^\w$])([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\(/g;
121
+ let m;
122
+ while ((m = re.exec(code)) !== null) {
123
+ const seg = m[1].split(".").pop() || "";
124
+ if (/^(if|for|while|switch|catch|return|new|case|do|super|this)$/.test(seg))
125
+ continue;
126
+ if (calls.length < 200 && !calls.includes(seg))
127
+ calls.push(seg);
128
+ }
129
+ return calls;
130
+ }
131
+ catch {
132
+ return [];
98
133
  }
99
134
  }
100
135
  function evaluatePolicy(ctx, rules) {
101
136
  const activeRules = rules || types_1.DEFAULT_POLICY;
102
137
  const violations = [];
138
+ // fail-closed(审计复检 2026-09-06):空数组是 truthy,原实现会
139
+ // 零规则 → 零违规 → ALLOW——空规则集必须拒绝而非放行
140
+ if (activeRules.length === 0) {
141
+ return {
142
+ passed: false,
143
+ verdict: "BLOCK",
144
+ rules: 0,
145
+ passed_rules: 0,
146
+ failed_rules: 1,
147
+ violations: [{
148
+ rule: {
149
+ type: "policy_config",
150
+ severity: "block",
151
+ description: "Empty policy ruleset is rejected (fail-closed)",
152
+ },
153
+ actual: "0 active rules",
154
+ expected: ">= 1 active rule",
155
+ detail: "empty ruleset passes nothing — refusing to ALLOW (fail-closed)",
156
+ }],
157
+ summary: "BLOCK: empty policy ruleset",
158
+ };
159
+ }
103
160
  for (const rule of activeRules) {
104
161
  switch (rule.type) {
105
162
  // ── Confidence ──
@@ -177,24 +234,44 @@ function evaluatePolicy(ctx, rules) {
177
234
  const minSeverity = rule.threshold ?? 2;
178
235
  const minConfidence = rule.require ?? 70;
179
236
  try {
180
- const { assessRisk } = require("../risk-model");
181
- // Extract call sequence from file (best-effort)
182
- const calls = ctx.certificate.validated ? [] : ["SSL_CTX_new", "SSL_connect"]; // fallback
183
- const risk = assessRisk(calls.length > 0 ? calls : ["init", "connect"]);
184
- const criticalOrHigh = risk.patterns.filter((p) => {
185
- const sevOrder = ["Low", "Medium", "High", "Critical"];
186
- return sevOrder.indexOf(p.severity) >= minSeverity && p.confidence >= minConfidence;
187
- });
188
- if (criticalOrHigh.length > 0) {
237
+ // 真实调用提取(certificate.file best-effort 词法扫描)——
238
+ // 审计修复(Kimi 2026-09-06):不再伪造 ["SSL_CTX_new","SSL_connect"]
239
+ // 假输入喂评估;输入不可得时按 fail-closed 计为违规
240
+ const calls = extractCallsBestEffort(ctx.certificate.file);
241
+ if (calls.length === 0) {
189
242
  violations.push({
190
243
  rule,
191
- actual: `${criticalOrHigh.length} risk pattern(s) severity threshold`,
192
- expected: `0 patterns at this severity+confidence level`,
193
- detail: criticalOrHigh.map((p) => `${p.patternName} (${p.severity}, ${p.confidence}%): ${p.detail}`).join("; "),
244
+ actual: "no call data extractable from certificate file",
245
+ expected: "risk assessment over real call sequence",
246
+ detail: "risk rule requires a readable file with calls; unavailable input is a violation (fail-closed)",
247
+ });
248
+ }
249
+ else {
250
+ const risk = (0, risk_model_1.assessRisk)(calls);
251
+ const criticalOrHigh = risk.patterns.filter((p) => {
252
+ const sevOrder = ["Low", "Medium", "High", "Critical"];
253
+ return sevOrder.indexOf(p.severity) >= minSeverity && p.confidence >= minConfidence;
194
254
  });
255
+ if (criticalOrHigh.length > 0) {
256
+ violations.push({
257
+ rule,
258
+ actual: `${criticalOrHigh.length} risk pattern(s) ≥ severity threshold`,
259
+ expected: `0 patterns at this severity+confidence level`,
260
+ detail: criticalOrHigh.map((p) => `${p.patternName} (${p.severity}, ${p.confidence}%): ${p.detail}`).join("; "),
261
+ });
262
+ }
195
263
  }
196
264
  }
197
- catch { /* risk model unavailable */ }
265
+ catch {
266
+ // fail-closed:risk model 不可用 → 显式违规,而非静默跳过
267
+ // (审计修复:原空 catch 使整条规则无声失效)
268
+ violations.push({
269
+ rule,
270
+ actual: "risk model unavailable",
271
+ expected: "risk assessment completed",
272
+ detail: "risk-model module failed to load — failing closed (explicit violation)",
273
+ });
274
+ }
198
275
  break;
199
276
  }
200
277
  // ── Knowledge Base Coverage ──
@@ -202,11 +279,20 @@ function evaluatePolicy(ctx, rules) {
202
279
  const minStable = rule.threshold ?? 3;
203
280
  let stableCount = 0;
204
281
  try {
205
- const { buildKnowledgeBase } = require("../protocol-knowledge");
206
- const kb = buildKnowledgeBase();
282
+ const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
207
283
  stableCount = kb.units.filter((u) => u.maturity === "stable").length;
208
284
  }
209
- catch { /* KB unavailable */ }
285
+ catch {
286
+ // fail-closed 显式化(审计修复):原空 catch 后 stableCount=0 恰好
287
+ // 触发违规,但消息误导为「0 stable assets」——改为明确的不可用违规
288
+ violations.push({
289
+ rule,
290
+ actual: "knowledge base unavailable",
291
+ expected: `>= ${minStable} stable assets`,
292
+ detail: "protocol-knowledge module failed to load — failing closed (explicit violation)",
293
+ });
294
+ break;
295
+ }
210
296
  if (stableCount < minStable) {
211
297
  violations.push({
212
298
  rule,
@@ -327,7 +413,13 @@ function loadEnterprisePolicyConfig(projectPath, configPath) {
327
413
  };
328
414
  }
329
415
  catch (e) {
330
- console.error(`⚠️ Failed to load enterprise policy config: ${e.message}. Using defaults.`);
331
- return emptyResult;
416
+ console.error(`⚠️ Failed to load enterprise policy config: ${e.message}.`);
417
+ // fail-closed 信号(审计复检 2026-09-06):与 loadPolicyConfig 同口径——
418
+ // 显式携带 configError,调用方拒绝静默评估
419
+ return {
420
+ ...emptyResult,
421
+ source: "built-in defaults (enterprise config error)",
422
+ configError: `Failed to parse ${cfgFile}: ${e.message}`,
423
+ };
332
424
  }
333
425
  }