progmune-runtime 3.7.0 → 3.7.1
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 +14 -0
- package/dist/call-sequence.js +27 -0
- package/dist/trust/engine.js +30 -13
- package/dist/trust/ssg-bridge.js +18 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.1] — 2026-08-23
|
|
4
|
+
|
|
5
|
+
### 修复:词段匹配门控(仅项目函数适用)
|
|
6
|
+
|
|
7
|
+
- `ssg-bridge` 的词段匹配(Strategy 2)增加 `projectFunctions` 门控:只对项目函数做词段匹配——它是为改名协议原语设计的(协议原语必然是项目内函数,如 S5 的 `create_active_session`),外部库调用(如 Node 的 `readFileSync`)经词段撞上 `read_file` 是纯噪声
|
|
8
|
+
- 外部 API 的语义桥接不受影响:alias 配置(Strategy 0b)与 domain 关键词(Strategy 3)照常工作;未提供集合时保持旧行为(向后兼容)
|
|
9
|
+
- 共享集合构造 `collectProjectFunctionNames`(`src/call-sequence.ts`,全名/裸名/小写变体三形态收录),生产引擎与协议盲测扫描器同款传入
|
|
10
|
+
|
|
11
|
+
### 修复:合并形态 ir.json 恢复 IR-first(3.5.0 起静默回退的回归)
|
|
12
|
+
|
|
13
|
+
- `extractCallSequencesFromIR` 与项目 IR 注解合并块兼容 `{ typeMap, functions }` 合并对象(execute/MCP 写盘形态)——此前 `Array.isArray` 守卫使所有 TS 项目自 3.5.0 起静默走正则回退,P4.5/P4.6 的 IR-first 语义在合并形态下未生效
|
|
14
|
+
- 配合词段门控后实测:自身 1966 函数 451 入口序列,SSG 违规 346→**2**(均真实命中,`writeTrajectoryFile`→`write_file`),Trust 总分 60→83(APPROVED)
|
|
15
|
+
- 协议盲测 v1.2 复测零漂移:66 可测金标 64 检出(Recall 97% / Precision 100% / 0 FP),S5 改名检测不受门控影响
|
|
16
|
+
|
|
3
17
|
## [3.7.0] — 2026-08-23
|
|
4
18
|
|
|
5
19
|
### 新增:P4.6 跨函数传播(入口展开 + 片段抑制)
|
package/dist/call-sequence.js
CHANGED
|
@@ -13,12 +13,39 @@
|
|
|
13
13
|
* 不做数据流/指针/分支分析;跨文件依赖 IR 的 calls[] 图。
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.isProjectFn = isProjectFn;
|
|
17
|
+
exports.collectProjectFunctionNames = collectProjectFunctionNames;
|
|
16
18
|
exports.buildCallSequences = buildCallSequences;
|
|
17
19
|
const MAX_DEPTH = 4;
|
|
18
20
|
/** 项目函数判定:有真实文件且非外部导入条目(external 条目无函数体可内联)。 */
|
|
19
21
|
function isProjectFn(f) {
|
|
20
22
|
return !f.external && !!f.file && f.file !== "(external)";
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 构建项目函数名集合——词段匹配门控用(ssg-bridge 的 projectFunctions 参数)。
|
|
26
|
+
* 每个项目函数收录三种形态:全名(FlowService.svc_x)、裸名(svc_x)与
|
|
27
|
+
* 小写变体(调用名大小写差异,如 createActiveSession vs createactivesession)。
|
|
28
|
+
*/
|
|
29
|
+
function collectProjectFunctionNames(ir) {
|
|
30
|
+
const names = new Set();
|
|
31
|
+
for (const f of ir) {
|
|
32
|
+
if (!isProjectFn(f))
|
|
33
|
+
continue;
|
|
34
|
+
const name = String(f.name || "");
|
|
35
|
+
if (!name)
|
|
36
|
+
continue;
|
|
37
|
+
const lower = name.toLowerCase();
|
|
38
|
+
names.add(name);
|
|
39
|
+
names.add(lower);
|
|
40
|
+
const dotIdx = name.lastIndexOf(".");
|
|
41
|
+
if (dotIdx >= 0) {
|
|
42
|
+
const bare = name.slice(dotIdx + 1);
|
|
43
|
+
names.add(bare);
|
|
44
|
+
names.add(bare.toLowerCase());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return names;
|
|
48
|
+
}
|
|
22
49
|
/**
|
|
23
50
|
* 从 IR 构建验证序列:入口函数展开 + 非入口抑制。
|
|
24
51
|
* @param ir - FunctionInfo 列表(TS 或 Python 提取器输出)
|
package/dist/trust/engine.js
CHANGED
|
@@ -727,6 +727,19 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
727
727
|
const callSequences = extractCallSequencesFromProject(ctx.projectPath, ctx.language, protocolRulesData ? new Set(protocolRulesData.rules.keys()) : undefined);
|
|
728
728
|
const flaggedCount = { value: 0 };
|
|
729
729
|
const cleanCount = { value: 0 };
|
|
730
|
+
// ── P4.6.1: 词段匹配门控的项目函数集合(best-effort,与注解合并共用 ir.json) ──
|
|
731
|
+
// 词段匹配只对项目函数适用(改名协议原语);外部库调用走 alias/关键词桥接。
|
|
732
|
+
let projectFunctions;
|
|
733
|
+
try {
|
|
734
|
+
const fs = require("fs");
|
|
735
|
+
const irPath = path.join(ctx.projectPath, "ir.json");
|
|
736
|
+
if (fs.existsSync(irPath)) {
|
|
737
|
+
const ir = JSON.parse(fs.readFileSync(irPath, "utf-8"));
|
|
738
|
+
const functions = Array.isArray(ir) ? ir : (ir.functions || []);
|
|
739
|
+
projectFunctions = (0, call_sequence_1.collectProjectFunctionNames)(functions);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch { /* best-effort */ }
|
|
730
743
|
// ── P4.5: 合并项目 IR 注解协议(IR 优先,缺 namespace 继承内置 JSON) ──
|
|
731
744
|
// 内置 protocols.json 的规则是通用弱约束(如 generate_jwt pre=[]),
|
|
732
745
|
// 项目文件里的 @protocol 注解才是项目真实协议(如 pre=[PASSWORD_VERIFIED])。
|
|
@@ -737,17 +750,18 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
737
750
|
const irPath = path.join(ctx.projectPath, "ir.json");
|
|
738
751
|
if (fs.existsSync(irPath)) {
|
|
739
752
|
const ir = JSON.parse(fs.readFileSync(irPath, "utf-8"));
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
753
|
+
// ir.json 两种形态:extractIR/extractIRPython 的裸数组、extractProjectIR
|
|
754
|
+
// 的 { typeMap, functions } 合并对象(execute/MCP 写盘)——统一取函数列表。
|
|
755
|
+
const functions = Array.isArray(ir) ? ir : (ir.functions || []);
|
|
756
|
+
for (const f of functions) {
|
|
757
|
+
if (!f.protocol)
|
|
758
|
+
continue;
|
|
759
|
+
const protocol = { ...f.protocol };
|
|
760
|
+
const existing = protocolRulesData.rules.get(String(f.name));
|
|
761
|
+
if (existing?.namespace && !protocol.namespace) {
|
|
762
|
+
protocol.namespace = existing.namespace;
|
|
750
763
|
}
|
|
764
|
+
protocolRulesData.rules.set(String(f.name), protocol);
|
|
751
765
|
}
|
|
752
766
|
}
|
|
753
767
|
}
|
|
@@ -844,7 +858,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
844
858
|
// to bridge real API names to abstract protocol function names.
|
|
845
859
|
if (protocolRulesData) {
|
|
846
860
|
try {
|
|
847
|
-
const ssgResult = (0, ssg_bridge_1.validateSequenceWithSSG)(semantic.steps, protocolRulesData.rules, protocolRulesData.namespaceInitialStates, seq.file, protocolRulesData.aliasIndex, protocolRulesData.wildcardAliases);
|
|
861
|
+
const ssgResult = (0, ssg_bridge_1.validateSequenceWithSSG)(semantic.steps, protocolRulesData.rules, protocolRulesData.namespaceInitialStates, seq.file, protocolRulesData.aliasIndex, protocolRulesData.wildcardAliases, projectFunctions);
|
|
848
862
|
ssgResults.push(ssgResult);
|
|
849
863
|
ssgTotalCalls += ssgResult.stats.totalCalls;
|
|
850
864
|
ssgMatchedCalls += ssgResult.stats.matchedCalls;
|
|
@@ -927,9 +941,12 @@ function extractCallSequencesFromIR(projectPath, keepNames) {
|
|
|
927
941
|
if (!fs.existsSync(irPath))
|
|
928
942
|
return [];
|
|
929
943
|
const ir = JSON.parse(fs.readFileSync(irPath, "utf-8"));
|
|
930
|
-
|
|
944
|
+
// ir.json 两种形态兼容:裸数组(extractIR / extractIRPython)与
|
|
945
|
+
// { typeMap, functions } 合并对象(extractProjectIR,execute/MCP 写盘)。
|
|
946
|
+
const functions = Array.isArray(ir) ? ir : (ir.functions || []);
|
|
947
|
+
if (!Array.isArray(functions) || functions.length === 0)
|
|
931
948
|
return [];
|
|
932
|
-
return (0, call_sequence_1.buildCallSequences)(
|
|
949
|
+
return (0, call_sequence_1.buildCallSequences)(functions, keepNames);
|
|
933
950
|
}
|
|
934
951
|
catch {
|
|
935
952
|
return [];
|
package/dist/trust/ssg-bridge.js
CHANGED
|
@@ -227,7 +227,7 @@ function normalizeName(name) {
|
|
|
227
227
|
* 2. Substring match (call contains rule name or vice versa)
|
|
228
228
|
* 3. Domain-guided keyword match
|
|
229
229
|
*/
|
|
230
|
-
function inferRuleName(apiName, domain, description, ruleNames, namespace, aliases, wildcardAliases) {
|
|
230
|
+
function inferRuleName(apiName, domain, description, ruleNames, namespace, aliases, wildcardAliases, projectFunctions) {
|
|
231
231
|
const normalized = normalizeName(apiName);
|
|
232
232
|
const lowerApi = apiName.toLowerCase();
|
|
233
233
|
const lowerDesc = description.toLowerCase();
|
|
@@ -262,7 +262,20 @@ function inferRuleName(apiName, domain, description, ruleNames, namespace, alias
|
|
|
262
262
|
// word segment in the normalized call name. This prevents "status" from
|
|
263
263
|
// matching "poll_status" via raw substring, since "status" must appear
|
|
264
264
|
// as a complete _-delimited segment.
|
|
265
|
+
//
|
|
266
|
+
// 门控(P4.6.1):词段匹配只对「项目函数」适用——它是为改名协议原语设计的
|
|
267
|
+
// (协议原语必然是项目内函数,如 S5 的 create_active_session)。外部库调用
|
|
268
|
+
// (如 Node 的 readFileSync)经词段撞上 read_file 是纯噪声:外部 API 的语义
|
|
269
|
+
// 桥接走 alias 配置(Strategy 0b)或 domain 关键词(Strategy 3),不走词段。
|
|
270
|
+
// 未提供 projectFunctions 时保持旧行为(向后兼容测试与无 IR 的调用方)。
|
|
271
|
+
const isProjectFn = !projectFunctions
|
|
272
|
+
|| projectFunctions.has(apiName)
|
|
273
|
+
|| projectFunctions.has(lowerApi)
|
|
274
|
+
|| projectFunctions.has(normalized)
|
|
275
|
+
|| (dotIdx >= 0 && projectFunctions.has(lowerApi.slice(dotIdx + 1)));
|
|
265
276
|
for (const ruleName of ruleNames) {
|
|
277
|
+
if (projectFunctions && !isProjectFn)
|
|
278
|
+
continue;
|
|
266
279
|
const ruleWords = ruleName.split("_");
|
|
267
280
|
const callWords = normalized.split("_");
|
|
268
281
|
// Rule must have at least 2 words for this strategy (single-word rules
|
|
@@ -303,7 +316,9 @@ function inferRuleName(apiName, domain, description, ruleNames, namespace, alias
|
|
|
303
316
|
* @param file — source file path (for violation reporting)
|
|
304
317
|
* @returns SSG validation result with violations and trace
|
|
305
318
|
*/
|
|
306
|
-
function validateSequenceWithSSG(steps, rules, namespaceInitialStates, file, aliasIndex, wildcardAliases
|
|
319
|
+
function validateSequenceWithSSG(steps, rules, namespaceInitialStates, file, aliasIndex, wildcardAliases,
|
|
320
|
+
/** 项目函数名集合(含裸名/全名/小写变体由调用方构造)——提供后词段匹配只对项目函数适用 */
|
|
321
|
+
projectFunctions) {
|
|
307
322
|
const allRuleNames = Array.from(rules.keys());
|
|
308
323
|
const aliases = aliasIndex || new Map();
|
|
309
324
|
const wildcards = wildcardAliases || new Map();
|
|
@@ -361,7 +376,7 @@ function validateSequenceWithSSG(steps, rules, namespaceInitialStates, file, ali
|
|
|
361
376
|
// Even if the semantic domain maps to "stateless", an alias may still
|
|
362
377
|
// map this call to a specific protocol rule with its own namespace.
|
|
363
378
|
// Priority: wildcard alias → exact alias → name match → word match → keyword
|
|
364
|
-
const ruleName = inferRuleName(step.api, step.domain, step.description, allRuleNames, namespace, aliases, wildcards);
|
|
379
|
+
const ruleName = inferRuleName(step.api, step.domain, step.description, allRuleNames, namespace, aliases, wildcards, projectFunctions);
|
|
365
380
|
// Use matched rule's namespace, falling back to domain-inferred namespace
|
|
366
381
|
const effectiveNamespace = ruleName
|
|
367
382
|
? (rules.get(ruleName)?.namespace || namespace)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.1",
|
|
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/",
|