progmune-runtime 3.7.31 → 3.7.35
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 +105 -0
- package/dist/extract-ir-taint-guard.test.js +372 -0
- package/dist/extract-ir-taint-structural.test.js +289 -0
- package/dist/extract-ir.js +263 -47
- package/dist/ir-staleness.js +233 -0
- package/dist/ir-staleness.test.js +228 -0
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +28 -2
- package/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const ir_staleness_1 = require("./ir-staleness");
|
|
41
|
+
/**
|
|
42
|
+
* ir.json 陈旧性判定的回归测试(2026-09-19)。
|
|
43
|
+
*
|
|
44
|
+
* 锁三件事:
|
|
45
|
+
* 1. ir.json 缺失 → missing(需提取)——保住原有的自动提取语义;
|
|
46
|
+
* 2. 源码比 ir.json 新 → source-newer(需重提)——本次新增的判别力;
|
|
47
|
+
* 3. ir.json 不早于源码 → fresh(沿用磁盘文件)——不做无谓的全量重提,
|
|
48
|
+
* 否则 skyvern 级项目每次扫描要多付分钟级成本。
|
|
49
|
+
*/
|
|
50
|
+
function tmpdir(tag) {
|
|
51
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), `pm-ir-stale-${tag}-`));
|
|
52
|
+
}
|
|
53
|
+
/** 把文件的 mtime 显式钉到某个 epoch ms(避免同一毫秒内写完两文件的歧义)。 */
|
|
54
|
+
function touch(file, content, mtimeMs) {
|
|
55
|
+
fs.writeFileSync(file, content);
|
|
56
|
+
fs.utimesSync(file, mtimeMs / 1000, mtimeMs / 1000);
|
|
57
|
+
}
|
|
58
|
+
(0, vitest_1.describe)("inspectIrFreshness", () => {
|
|
59
|
+
(0, vitest_1.it)("ir.json 缺失 → missing,stale=true(触发自动提取)", () => {
|
|
60
|
+
const dir = tmpdir("missing");
|
|
61
|
+
try {
|
|
62
|
+
touch(path.join(dir, "app.ts"), "export const x = 1;", 1700000000000);
|
|
63
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
64
|
+
(0, vitest_1.expect)(r.exists).toBe(false);
|
|
65
|
+
(0, vitest_1.expect)(r.stale).toBe(true);
|
|
66
|
+
(0, vitest_1.expect)(r.reason).toBe("missing");
|
|
67
|
+
(0, vitest_1.expect)(r.irMtimeMs).toBeNull();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)("源码比 ir.json 新 → source-newer(本次修复的核心:原先会被静默沿用)", () => {
|
|
74
|
+
const dir = tmpdir("newer");
|
|
75
|
+
try {
|
|
76
|
+
const irFile = path.join(dir, "ir.json");
|
|
77
|
+
const srcFile = path.join(dir, "app.ts");
|
|
78
|
+
touch(irFile, "[]", 1700000000000);
|
|
79
|
+
touch(srcFile, "export const x = 1;", 1700000100000); // 晚 100s
|
|
80
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
81
|
+
(0, vitest_1.expect)(r.exists).toBe(true);
|
|
82
|
+
(0, vitest_1.expect)(r.stale).toBe(true);
|
|
83
|
+
(0, vitest_1.expect)(r.reason).toBe("source-newer");
|
|
84
|
+
(0, vitest_1.expect)(r.newestSourcePath).toBe(srcFile);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)("ir.json 比源码新 → fresh(不重提,避免分钟级全量重提)", () => {
|
|
91
|
+
const dir = tmpdir("fresh");
|
|
92
|
+
try {
|
|
93
|
+
touch(path.join(dir, "app.ts"), "export const x = 1;", 1700000000000);
|
|
94
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000100000);
|
|
95
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
96
|
+
(0, vitest_1.expect)(r.stale).toBe(false);
|
|
97
|
+
(0, vitest_1.expect)(r.reason).toBe("fresh");
|
|
98
|
+
(0, vitest_1.expect)(r.scannedFiles).toBe(1);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)("同刻写出(源码→ir.json 的常见顺序)→ fresh(不得推入全量重提)", () => {
|
|
105
|
+
const dir = tmpdir("same");
|
|
106
|
+
try {
|
|
107
|
+
touch(path.join(dir, "app.ts"), "export const x = 1;", 1700000000000);
|
|
108
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000000000);
|
|
109
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
110
|
+
(0, vitest_1.expect)(r.stale).toBe(false);
|
|
111
|
+
(0, vitest_1.expect)(r.reason).toBe("fresh");
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
(0, vitest_1.it)("子目录源码也算源码(只看根目录下的话会漏判)", () => {
|
|
118
|
+
const dir = tmpdir("nested");
|
|
119
|
+
try {
|
|
120
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000000000);
|
|
121
|
+
const nested = path.join(dir, "src", "deep");
|
|
122
|
+
fs.mkdirSync(nested, { recursive: true });
|
|
123
|
+
touch(path.join(nested, "handler.ts"), "export function handle() {}", 1700000500000);
|
|
124
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
125
|
+
(0, vitest_1.expect)(r.stale).toBe(true);
|
|
126
|
+
(0, vitest_1.expect)(r.newestSourcePath).toContain(path.join("src", "deep", "handler.ts"));
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
(0, vitest_1.it)("node_modules 等非源码目录被跳过(成本约束:不得遍历依赖树)", () => {
|
|
133
|
+
const dir = tmpdir("skip");
|
|
134
|
+
try {
|
|
135
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000500000);
|
|
136
|
+
const nm = path.join(dir, "node_modules", "pkg");
|
|
137
|
+
fs.mkdirSync(nm, { recursive: true });
|
|
138
|
+
touch(path.join(nm, "index.js"), "module.exports = 1;", 1700000900000);
|
|
139
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
140
|
+
(0, vitest_1.expect)(r.stale).toBe(false);
|
|
141
|
+
(0, vitest_1.expect)(r.reason).toBe("no-sources");
|
|
142
|
+
(0, vitest_1.expect)(r.scannedFiles).toBe(0);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)("非源码后缀(png/md/json)不影响判定", () => {
|
|
149
|
+
const dir = tmpdir("ext");
|
|
150
|
+
try {
|
|
151
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000500000);
|
|
152
|
+
touch(path.join(dir, "README.md"), "hello", 1700000900000);
|
|
153
|
+
touch(path.join(dir, "logo.png"), "x", 1700000900000);
|
|
154
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
155
|
+
(0, vitest_1.expect)(r.stale).toBe(false);
|
|
156
|
+
(0, vitest_1.expect)(r.reason).toBe("no-sources");
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
(0, vitest_1.it)("源码规模超预算 → exceedsAutoBudget(auto 模式只警告,不自动全量重提)", () => {
|
|
163
|
+
const dir = tmpdir("budget");
|
|
164
|
+
try {
|
|
165
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000000000);
|
|
166
|
+
for (let i = 0; i < 5; i++) {
|
|
167
|
+
touch(path.join(dir, `f${i}.ts`), "export const x = 1;", 1700000100000);
|
|
168
|
+
}
|
|
169
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir, undefined, { autoBudget: 3 });
|
|
170
|
+
(0, vitest_1.expect)(r.stale).toBe(true); // 陈旧判定不受预算影响
|
|
171
|
+
(0, vitest_1.expect)(r.exceedsAutoBudget).toBe(true);
|
|
172
|
+
(0, vitest_1.expect)(r.scannedFiles).toBe(5);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
(0, vitest_1.it)("遍历被上限截断时标记 evidenceComplete=false(超大仓库不得静默全量重提)", () => {
|
|
179
|
+
const dir = tmpdir("trunc");
|
|
180
|
+
try {
|
|
181
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000000000);
|
|
182
|
+
for (let i = 0; i < 5; i++) {
|
|
183
|
+
touch(path.join(dir, `f${i}.ts`), "export const x = 1;", 1700000100000);
|
|
184
|
+
}
|
|
185
|
+
// maxStats=2:扫到 2 个就停 —— stale 仍为真(证据已经够),但 fresh 不可信
|
|
186
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir, undefined, { maxStats: 2 });
|
|
187
|
+
(0, vitest_1.expect)(r.stale).toBe(true);
|
|
188
|
+
(0, vitest_1.expect)(r.truncated).toBe(true);
|
|
189
|
+
(0, vitest_1.expect)(r.evidenceComplete).toBe(false);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
(0, vitest_1.it)("正常仓库(未截断)fresh 判定证据完整", () => {
|
|
196
|
+
const dir = tmpdir("complete");
|
|
197
|
+
try {
|
|
198
|
+
touch(path.join(dir, "app.ts"), "export const x = 1;", 1700000000000);
|
|
199
|
+
touch(path.join(dir, "ir.json"), "[]", 1700000100000);
|
|
200
|
+
const r = (0, ir_staleness_1.inspectIrFreshness)(dir);
|
|
201
|
+
(0, vitest_1.expect)(r.evidenceComplete).toBe(true);
|
|
202
|
+
(0, vitest_1.expect)(r.reason).toBe("fresh");
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
(0, vitest_1.it)("逃逸阀:默认 auto", () => {
|
|
209
|
+
const prev = process.env.PROGMUNE_IR_REEXTRACT;
|
|
210
|
+
delete process.env.PROGMUNE_IR_REEXTRACT;
|
|
211
|
+
(0, vitest_1.expect)((0, ir_staleness_1.reextractMode)()).toBe("auto");
|
|
212
|
+
(0, vitest_1.expect)((0, ir_staleness_1.reextractEnabled)()).toBe(true);
|
|
213
|
+
if (prev !== undefined)
|
|
214
|
+
process.env.PROGMUNE_IR_REEXTRACT = prev;
|
|
215
|
+
});
|
|
216
|
+
(0, vitest_1.it)("逃生阀:always / never 语义", () => {
|
|
217
|
+
const prev = process.env.PROGMUNE_IR_REEXTRACT;
|
|
218
|
+
process.env.PROGMUNE_IR_REEXTRACT = "always";
|
|
219
|
+
(0, vitest_1.expect)((0, ir_staleness_1.reextractMode)()).toBe("always");
|
|
220
|
+
process.env.PROGMUNE_IR_REEXTRACT = "never";
|
|
221
|
+
(0, vitest_1.expect)((0, ir_staleness_1.reextractMode)()).toBe("never");
|
|
222
|
+
(0, vitest_1.expect)((0, ir_staleness_1.reextractEnabled)()).toBe(false);
|
|
223
|
+
if (prev === undefined)
|
|
224
|
+
delete process.env.PROGMUNE_IR_REEXTRACT;
|
|
225
|
+
else
|
|
226
|
+
process.env.PROGMUNE_IR_REEXTRACT = prev;
|
|
227
|
+
});
|
|
228
|
+
});
|
package/dist/sdk.js
CHANGED
|
@@ -20,7 +20,7 @@ const risk_model_1 = require("./risk-model");
|
|
|
20
20
|
const protocol_knowledge_1 = require("./protocol-knowledge");
|
|
21
21
|
const evidence_repository_1 = require("./evidence-repository");
|
|
22
22
|
/** Runtime version — stable public identifier. Internal layers evolve underneath. */
|
|
23
|
-
exports.RUNTIME_VERSION = "3.7.
|
|
23
|
+
exports.RUNTIME_VERSION = "3.7.35";
|
|
24
24
|
function verify(filePath) {
|
|
25
25
|
const cert = (0, certify_1.certify)(filePath);
|
|
26
26
|
const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
|
package/dist/trust/engine.js
CHANGED
|
@@ -62,6 +62,7 @@ const api_semantic_mapper_1 = require("./api-semantic-mapper");
|
|
|
62
62
|
const protocol_domain_validator_1 = require("./protocol-domain-validator");
|
|
63
63
|
const annotation_suggest_1 = require("../annotation-suggest");
|
|
64
64
|
const call_graph_propagator_1 = require("./call-graph-propagator");
|
|
65
|
+
const ir_staleness_1 = require("../ir-staleness");
|
|
65
66
|
const fastapi_detector_1 = require("../frameworks/fastapi-detector");
|
|
66
67
|
const django_detector_1 = require("../frameworks/django-detector");
|
|
67
68
|
const flask_detector_1 = require("../frameworks/flask-detector");
|
|
@@ -87,7 +88,10 @@ const extract_ir_java_1 = require("../extract-ir-java");
|
|
|
87
88
|
* C → extractIRC 合并形态 { typeMap, functions })——注解合并(P4.5)依赖
|
|
88
89
|
* ir.json,调用方【无需】手动 extractProjectIR/写盘(曾是对 C 注解静默
|
|
89
90
|
* 失效的文档/API 陷阱,回归测试见 tests/trust/engine.test.ts「DSH 陷阱」)。
|
|
90
|
-
* 若项目已有 ir.json
|
|
91
|
+
* 若项目已有 ir.json:原语义是「以现有文件为准(不覆盖)」——该语义会让
|
|
92
|
+
* 【源码已改、ir.json 未更新】的场景静默读旧 IR(2026-09-19 修正):
|
|
93
|
+
* mtime 比对发现有源码新于 ir.json 时重提;否则沿用磁盘文件(不做无谓的全量重提)。
|
|
94
|
+
* 逃生阀 PROGMUNE_IR_REEXTRACT=never 可回到旧语义(会打「IR 可能陈旧」警告)。
|
|
91
95
|
*/
|
|
92
96
|
async function evaluateTrust(ctx) {
|
|
93
97
|
const engineVersion = "trust-runtime-v1.0.0";
|
|
@@ -1228,7 +1232,29 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
1228
1232
|
if (extractFn) {
|
|
1229
1233
|
try {
|
|
1230
1234
|
const fs = require("fs");
|
|
1231
|
-
|
|
1235
|
+
// 陈旧性判定(2026-09-19):原来只看 existsSync——源码改了、ir.json 没更新时
|
|
1236
|
+
// 引擎静默吃旧 IR(fr-005 首扫报 0、污点试点「标记注入但 trust 报 0」同源)。
|
|
1237
|
+
// 改为 mtime 比对:仅当有源码比 ir.json 新时才重提,避免全量重提的分钟级成本。
|
|
1238
|
+
const freshness = (0, ir_staleness_1.inspectIrFreshness)(ctx.projectPath);
|
|
1239
|
+
const mode = (0, ir_staleness_1.reextractMode)();
|
|
1240
|
+
// 陈旧的判定由 mtime 给出;是否【自动】重提还要过成本闸:
|
|
1241
|
+
// ① evidenceComplete 为假 → 遍历被截断,证据不可信;
|
|
1242
|
+
// ② exceedsAutoBudget → 源码规模超预算(万级源文件的本仓库实测把
|
|
1243
|
+
// 单次 evaluateTrust 推过 30s 超时),重提成本不该由默认路径承担。
|
|
1244
|
+
// 两种情况都改为显式警告;语料复测等场景用 PROGMUNE_IR_REEXTRACT=always 承担。
|
|
1245
|
+
const shouldExtract = !freshness.exists ||
|
|
1246
|
+
(freshness.stale &&
|
|
1247
|
+
(mode === "always" ||
|
|
1248
|
+
(mode === "auto" &&
|
|
1249
|
+
freshness.evidenceComplete &&
|
|
1250
|
+
!freshness.exceedsAutoBudget)));
|
|
1251
|
+
if (freshness.exists && freshness.stale && !shouldExtract) {
|
|
1252
|
+
console.warn(`[progmune] ⚠️ IR 可能陈旧:${freshness.newestSourcePath} 新于 ir.json` +
|
|
1253
|
+
`(mode=${mode}${freshness.exceedsAutoBudget ? ",源码超出重提预算" : ""}` +
|
|
1254
|
+
`${freshness.truncated ? ",遍历被截断" : ""};` +
|
|
1255
|
+
`需重提取运行 PROGMUNE_IR_REEXTRACT=always 或删除 ir.json)`);
|
|
1256
|
+
}
|
|
1257
|
+
if (shouldExtract) {
|
|
1232
1258
|
const ir = extractFn();
|
|
1233
1259
|
// C 走合并形态(与 execute/MCP 写盘一致);TS/Python 保持裸数组形态
|
|
1234
1260
|
const payload = lang === "c" ? { typeMap: {}, functions: ir } : ir;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.35",
|
|
4
4
|
"description": "Progmune — AI Trust Decision Engine. Verify AI-generated code before it reaches production. Outputs APPROVED / NEEDS_REVIEW / BLOCKED with evidence.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/",
|