progmune-runtime 3.7.5 → 3.7.7
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 +37 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/c-aliases.json +13 -0
- package/dist/annotation-suggest.js +200 -0
- package/dist/annotation-suggest.test.js +117 -0
- package/dist/call-sequence.js +7 -1
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +52 -11
- package/dist/trust/protocol-domain-validator.js +11 -1
- package/dist/trust/protocol-domain-validator.test.js +60 -0
- package/dist/trust/ssg-bridge.js +65 -25
- package/dist/trust/ssg-bridge.test.js +76 -0
- package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +20 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.7] — 2026-08-28
|
|
4
|
+
|
|
5
|
+
### C 注解采纳体验(采纳生死线工具)
|
|
6
|
+
|
|
7
|
+
- **注解建议引擎**(`src/annotation-suggest.ts`):确定性启发式(无 LLM)——按函数名词汇(3.7.6 金标 5/5 真实注解反推的词表)× 已注解状态,生成原语注解候选(verify/establish/guard/open/close 角色 + 命名空间/状态转移/注释块模板/置信度/命中理由/掩蔽风险标记);已注解、规则名、外部函数自动排除;两遍计算 maskRisk(体内调用规则原语或本批同被建议函数)
|
|
8
|
+
- **CLI 扫描模式**(`scripts/c-annotate.js --scan <dir> [--write] [--all] [--include-resource]`):dry-run 默认;--write 自动插入函数定义上方(写入后自动刷新 ir.json 防陈旧);保守门控(全部实测依据):establish/掩蔽风险跳过(不提供强制开关)、open/close 跳过(--include-resource 强制)
|
|
9
|
+
- **引擎加性字段**:`evaluateTrust.annotationSuggestions`(仅 C 生成;TS/Python 无该字段——零漂移)
|
|
10
|
+
- **验收(REALWORLD_C_V7.md)**:无注解 uftpd 副本金标恢复率 **4/4 角色正确**(check_user_pass=verify / handle_PASS=establish / do_RETR / do_STOR=guard);自动应用 7 条后与手写金标**等价**(真实代码 0 SSG FP、植入违规 2/2 精确定位、NEEDS_REVIEW 72 同分)
|
|
11
|
+
- **三个实测安全发现(自动写入的门控依据)**:①掩蔽风险——establish 注解把手握违规的流函数变「原语」(函数内顺序不检查)后植入违规被掩蔽(实测 2 处);②资源生命周期注解自动应用触发跨函数窗口 FP 类(实测 28 条,open/close 分处不同函数窗口);③new_session 词汇误判守卫(实测 4 FP,模式收紧为 channel 类)
|
|
12
|
+
- 回归:15 个新单元测试;引擎相关套件 98/98;Python 盲测 v1.2 零漂移(64);C 应用级金标 F1=95.7% 不变;演示重扫 SSG 结果不变
|
|
13
|
+
|
|
14
|
+
## [3.7.6] — 2026-08-28
|
|
15
|
+
|
|
16
|
+
### C 生产级路径收官:金标 5/5 + 采纳案例 + 正则层噪声治理
|
|
17
|
+
|
|
18
|
+
- **金标 4/5(libssh 回调分发认证)**(`demo-real-c-libssh-cb/` + `REALWORLD_C_V6.md`):真实 `samplesshd-cb.c`(359 行)逐字——现代回调分发 API(`ssh_server_callbacks_struct` 的 `auth_password_function` / `channel_open_request_session_function`,决策记录指定的最优模块)。2 注解 → APPROVED 82、真实代码 0 FP、植入 `cb_session_no_auth` 精确定位;认证完成跃迁在 libssh 内部 + main 循环条件(L3 边界),establish 由演示层 wrapper 表达(V5 wrapper 模式)
|
|
19
|
+
- **金标 5/5(uftpd 数据传送授权)**:采纳项目第二个协议——真实 `do_RETR`/`do_STOR` + 2 注解(pre AUTHENTICATED → post AUTHORIZED,镜像 check_resource_ownership 语义)→ 真实代码 0 FP、`ftp_transfer_no_login` 精确定位(fixPath → `establish_login`);金标累计 5/5 全部 ~2-3 注解/协议
|
|
20
|
+
- **发现 G5(规则面缺口入册)**:SSG 状态机 per-namespace——内置 `check_resource_ownership`(data_integrity,pre=[AUTHENTICATED])永远不可满足(AUTHENTICATED 在 auth 命名空间,src/tests 全仓库零引用)——规则面设计缺口,非引擎缺陷
|
|
21
|
+
- **正则层噪声治理(V5 发现 4 落地)**:`PLAINTEXT_AUTH_WITHOUT_TLS` 加 `languages` 门控排除 C(真实语料证据 3 FP / 0 TP:libssh 演示 1 + uftpd 采纳案例 2——FTP/SSH 应用层本就明文,Web/TLS 语义对 C 无意义);SSH 主机密钥规则保留全语言(libssh 演示 1 TP)。`checkSpecificViolations` 加 `language` 参数、engine 传 `ctx.language`、5 个回归测试;**双零漂移**:Python 盲测 v1.2 64 违规(仅时间戳差)、C 应用级金标 F1=95.7% 不变;uftpd 重扫 PLAINTEXT ×2 消失、libssh 演示 FP 消失 TP 保留
|
|
22
|
+
- **标签升级已拍板**:C「⚠️ 研究」→「✅ 注解驱动协议验证(Beta)」——README 双语、覆盖矩阵双语、c-language-status、CLAUDE.md 同步翻新;能力边界如实保留(未注解不检测、TLS 级无覆盖)
|
|
23
|
+
|
|
24
|
+
### C 定位拍板「注解驱动」+ 库边界机制 + 精度修复
|
|
25
|
+
|
|
26
|
+
- **定位拍板**(Decision record 定稿):C = 注解驱动协议验证(研究级)——未注解自动检测 0 TP/3 FP,两条自动检测桥(C 库别名注册表、方言解析器)不排期;文档翻转:README 双语 + 覆盖矩阵双语 C 行「⚠️ 注解驱动(研究级)」
|
|
27
|
+
- **库边界机制(孵化器)**:两层——项目原语注解(`@progmune`,不迁移)+ **库边界别名**(`.progmune_aliases.json` → 共享表 `c-aliases.json`,跨项目迁移)。新增:`scripts/c-annotate.js`(注释块模板 + 别名条目建议脚手架)、`scripts/c-alias-propose.js`(别名校验 + 回写提案 + 人工确认门);引擎 `loadProtocolRules` 合并共享表 **confirmed** 条目(proposed 不生效、不覆盖全局/项目别名、规则不存在跳过);`c-aliases.json` 入 npm 包
|
|
28
|
+
- **库边界演示**(`demo-real-c-libssh/` + `REALWORLD_C_V3.md`):真实 libssh authentication.c 逐字 + 1 别名 + 1 注解 → SSG 层 0 误报、植入 missing-auth 精确定位;正则防护层同跑:SSH_NO_HOST_KEY_CHECK 为**真发现**(示例确实不验证主机密钥)、PLAINTEXT_AUTH_WITHOUT_TLS 为 FP(Web 语义规则误映射 SSH,如实记录)
|
|
29
|
+
- **发现**:多机制认证重试循环的状态机语义缺口(verify 类规则 pre 不可重入——全机制别名映射会误报正常重试流,候选:可重试标记);fixPath 反向映射(规则名 → 库调用名)候选
|
|
30
|
+
- **陷阱修复(DSH 复测发现)**:`evaluateTrust` 的注解合并依赖 ir.json 写盘,而自动提取此前仅 TS/JS 生效——C/Python 项目直接调用时注解静默失效。现在按语言分派提取器自动写盘(C 走合并形态;TS/JS 路径零变化)+ 回归测试;提取器改静态导入(vitest 下 lazy require 的 CJS 互操作不可靠——该分支从未被测试触发过)
|
|
31
|
+
- **精度修复**:真实语料 24 FP → 3 FP(-87.5%)
|
|
32
|
+
|
|
33
|
+
### C 精度修复:真实语料 24 FP → 3 FP(-87.5%)
|
|
34
|
+
|
|
35
|
+
- **Strategy 1 normalized 门控**:Windows API(ReadFile/WriteFile/DeleteFile)经 CamelCase→snake_case 规范化撞上内置规则名是 11/24 FP 的主导源——normalized 分支套用词段门控同款 projectFunctions 门(外部 API 不桥接;注解桥接不受影响——注解原语必为项目函数;原始名精确匹配不限门控)
|
|
36
|
+
- **endState 直接调用溯源**:12/24 FP 是 nginx 回调式生命周期(open 在内联 helper 链、close 在指针注册的回调里——L3 级不可见)——`CallSequence.directCalls` + `validateSequenceWithSSG` 的 `entryDirectCalls` 参数,仅当资源获取调用是入口的直接调用时报告 endState(helper 获取不归因入口)
|
|
37
|
+
- **结果**:真实语料四仓库 24 flags → **3 flags**(nginx 14→0、redis 0、libssh 1、openssl 2;残留 3 条是词段桥接按设计命中项目函数)。双回归门全过:Python 盲测 v1.2 64 违规零漂移(仅时间戳差异)、C 应用级金标 **TP 11/FP 1/FN 0 → F1=95.7% 不变**(leak_file 的 endState TP 保留)、引擎相关套件 120/120
|
|
38
|
+
- **战略记录**:C 产品定位(自动检测 vs 注解驱动)待拍板——写入 `docs/c-language-status.md` Decision record;证据偏向注解驱动(精度修复后未注解自动检测仍 0 TP),金标扩量延后至定位拍板
|
|
39
|
+
|
|
3
40
|
## [3.7.5] — 2026-08-27
|
|
4
41
|
|
|
5
42
|
### C 真实语料验证 + 注解驱动演示 + 引擎修复(DSH 双轮评审合入)
|
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Progmune is honest about what it can and cannot verify.
|
|
|
103
103
|
|----------|--------|----------|
|
|
104
104
|
| **TypeScript / JavaScript** | ✅ Production | Blind benchmark: **recall 98.5% / precision 100%** (795 gold findings, 100 projects) |
|
|
105
105
|
| **Python** | ✅ Production | Blind benchmark: **recall 100% / precision 100%** (729 gold findings, 90 projects); real-world validation: PyGoat (OWASP vulnerable-by-design Django app) **67 TP / 0 FP, 100% labeled precision**; three well-written apps (django/fastapi realworld, django-unicorn) with 0 false-positive true findings |
|
|
106
|
-
| **C** |
|
|
106
|
+
| **C** | ✅ Annotation-driven (Beta) | IR extraction via the registry + SSG state machine; **annotate ~2-3 protocol primitives per protocol to get trusted verification** (gold 5/5 on real modules: redis ACL / libssh client / libssh server / libssh callback-dispatch / uftpd transfer-auth — all 0 FP with precise violation localization; app-level gold v2: **P=91.7% / R=100% / F1=95.7%**). Unannotated auto-detection stays out of scope (0 TP on real corpus — positioning decision recorded in the C Language Status doc); TLS-level coverage still absent. See [C Language Status](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md). |
|
|
107
107
|
| **Go, Java** | ❌ None | Planned |
|
|
108
108
|
|
|
109
109
|
**Multi-language IR (registry-based).** TypeScript (ts-morph) and Python (AST) extractors are registered entries in one registry (`src/extract-project-ir.ts`); `extractProjectIR` merges every detected language into a single function IR shared by the agent loop, `execute()`, and the MCP server — the agent composes function-protocol chains across both languages. Adding a language (Go, Java, …) = register one extractor entry; callers don't change.
|
|
@@ -152,9 +152,9 @@ Public, reproducible precision data. All numbers measured against gold-annotated
|
|
|
152
152
|
|
|
153
153
|
→ [Real-world validation report](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/REALWORLD_APP_V1.md) · [Benchmark baseline](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/BASELINE_v6.md)
|
|
154
154
|
|
|
155
|
-
### C (IR extraction +
|
|
155
|
+
### C (IR extraction + annotation-driven protocol verification — Beta)
|
|
156
156
|
|
|
157
|
-
Since 3.7.4, C has an IR extractor in the multi-language registry: C projects flow into IR-first sequence validation + the SSG state machine. App-level protocol lifecycles (auth/db/file/payment) are verifiable on C — app-level gold v2: **11/11 violations caught (Recall 100%), 1 FP, F1=95.7
|
|
157
|
+
Since 3.7.4, C has an IR extractor in the multi-language registry: C projects flow into IR-first sequence validation + the SSG state machine. App-level protocol lifecycles (auth/db/file/payment) are verifiable on C — app-level gold v2: **11/11 violations caught (Recall 100%), 1 FP, F1=95.7%**. Annotation-driven verification is the production shape (Beta as of 3.7.6): gold 5/5 on real modules (redis ACL, libssh client/server/callback-dispatch, uftpd transfer-auth) + one independent adoption case (uftpd) — all 0 FP with precise violation localization, at a steady cost of ~2-3 annotations per protocol. Unannotated auto-detection is out of scope (0 TP on real corpus). Extraction covers large real repos in seconds (openssl 15.5k / redis 5.7k / curl 4.2k functions, gold-function recovery 97–100%). Two boundaries remain: the old regex-route gold benchmark F1=16.5% measured **TLS-level misuse** (SSG has no TLS state machines — that gap is unchanged), and L3/L4 conclusions stand (function-pointer dispatch is statically invisible; no pointer/CFG analysis planned). See [C Language Status](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md) for the full picture and reasoning.
|
|
158
158
|
|
|
159
159
|
### P0-P3 Rule Injection (2026-08)
|
|
160
160
|
|
package/README.zh-CN.md
CHANGED
|
@@ -103,7 +103,7 @@ Progmune 对能验证什么、不能验证什么保持诚实。
|
|
|
103
103
|
|------|------|------|
|
|
104
104
|
| **TypeScript / JavaScript** | ✅ 生产 | 盲测基准:**召回 98.5% / 精确率 100%**(795 条 gold finding,100 个项目) |
|
|
105
105
|
| **Python** | ✅ 生产 | 盲测基准:**召回 100% / 精确率 100%**(729 条 gold finding,90 个项目);真实应用验证:PyGoat(OWASP 故意脆弱 Django 应用)**67 TP / 0 FP,标记精确率 100%**;三个良构应用(django/fastapi realworld、django-unicorn)0 误报真阳性 |
|
|
106
|
-
| **C** |
|
|
106
|
+
| **C** | ✅ 注解驱动(Beta) | IR 提取接入注册表 + SSG 状态机;**每协议标注 ~2-3 个原语即获得可信验证**(真实模块金标 5/5:redis ACL / libssh 客户端 / libssh 服务端 / libssh 回调分发 / uftpd 传送授权——全部 0 误报 + 违规精确定位;应用级金标 v2:**P=91.7% / R=100% / F1=95.7%**)。未注解自动检测不在范围(真实语料 0 TP——定位决议见 C 语言状态文档);TLS 级覆盖仍无。见 [C 语言状态](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md)。 |
|
|
107
107
|
| **Go, Java** | ❌ 无 | 规划中 |
|
|
108
108
|
|
|
109
109
|
**多语言 IR(注册表式)。** TypeScript(ts-morph)与 Python(AST)提取器是同一注册表(`src/extract-project-ir.ts`)中的注册项;`extractProjectIR` 把检测到的所有语言合并为一份函数 IR,由 agent loop、`execute()` 与 MCP server 共享——agent 可在两种语言上编排函数协议链。新增语言(Go、Java……)= 注册一条提取器,调用方零改动。
|
|
@@ -152,9 +152,9 @@ Progmune 对能验证什么、不能验证什么保持诚实。
|
|
|
152
152
|
|
|
153
153
|
→ [真实验证报告](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/REALWORLD_APP_V1.md) · [基准基线](https://github.com/shenlian19831109/progmune-runtime/blob/main/blind-benchmark/BASELINE_v6.md)
|
|
154
154
|
|
|
155
|
-
### C(IR 提取 +
|
|
155
|
+
### C(IR 提取 + 注解驱动协议验证——Beta)
|
|
156
156
|
|
|
157
|
-
3.7.4 起 C 拥有多语言注册表中的 IR 提取器:C 项目进入 IR-first 序列验证 + SSG 状态机。应用级协议生命周期(认证/数据库/文件/支付)在 C 上可验证——应用级金标 v2:**11/11 违规全检出(召回 100%)、1 误报、F1=95.7
|
|
157
|
+
3.7.4 起 C 拥有多语言注册表中的 IR 提取器:C 项目进入 IR-first 序列验证 + SSG 状态机。应用级协议生命周期(认证/数据库/文件/支付)在 C 上可验证——应用级金标 v2:**11/11 违规全检出(召回 100%)、1 误报、F1=95.7%**。注解驱动是 C 的生产形态(3.7.6 起 Beta):真实模块金标 5/5(redis ACL、libssh 客户端/服务端/回调分发、uftpd 传送授权)+ 1 个独立采纳案例(uftpd)——全部 0 误报 + 违规精确定位,标注成本稳定在每协议 ~2-3 条。未注解自动检测不在范围(真实语料 0 TP)。提取覆盖大型真实仓库(openssl 15.5k / redis 5.7k / curl 4.2k 函数,秒级,黄金函数恢复率 97–100%)。两条边界不变:旧正则口径黄金基准 F1=16.5% 测的是 **TLS 级误用**(SSG 无 TLS 状态机,该口径不变);L3/L4 结论维持(函数指针分发静态不可见;无指针/CFG 分析计划)。详见 [C 语言状态](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/c-language-status.md)。
|
|
158
158
|
|
|
159
159
|
### P0-P3 规则注入(2026-08)
|
|
160
160
|
|
package/c-aliases.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "C 库边界别名共享表——注解驱动定位的孵化器燃料(跨项目迁移)",
|
|
3
|
+
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"call": "ssh_userauth_password",
|
|
6
|
+
"rule": "verify_password",
|
|
7
|
+
"status": "confirmed",
|
|
8
|
+
"proposedBy": "demo-real-c-libssh",
|
|
9
|
+
"proposedAt": "2026-08-27",
|
|
10
|
+
"confirmedAt": "2026-08-27"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* annotation-suggest.ts — C 注解建议引擎(确定性启发式,无 LLM)
|
|
4
|
+
*
|
|
5
|
+
* 注解驱动定位的「采纳生死线」:未注解 C 项目的 SSG 层静默(0 flags——
|
|
6
|
+
* 看不见问题,也看不见该在哪下注)。本模块按函数名词汇 + 已注解状态,
|
|
7
|
+
* 为项目生成「原语注解建议清单」——每条建议含角色(verify/establish/
|
|
8
|
+
* guard/open/close)、命名空间、状态转移与可直接粘贴的注释块模板。
|
|
9
|
+
*
|
|
10
|
+
* 设计原则:
|
|
11
|
+
* - 纯名称启发式,诚实标注置信度(high=多证据/强动词,medium=单证据);
|
|
12
|
+
* 不假装语义理解——建议是「填空起点」,人工确认后生效(与
|
|
13
|
+
* c-alias-propose.js 的人工确认门同一哲学)。
|
|
14
|
+
* - 词汇来自 3.7.6 金标 5/5 的真实注解反推(check_user_pass=verify、
|
|
15
|
+
* handle_PASS=establish、do_RETR/do_STOR=守卫、auth_password=verify、
|
|
16
|
+
* new_session_channel=会话守卫、open/close_data_connection=资源生命周期)。
|
|
17
|
+
* - 已注解函数、规则名函数(按名即命中,无需注解)、外部函数不重复建议。
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.suggestAnnotations = suggestAnnotations;
|
|
21
|
+
const ROLE_DEFS = [
|
|
22
|
+
{
|
|
23
|
+
// 凭证比对(密码/口令/凭证 × 比对动词)——verify_password 同款语义
|
|
24
|
+
role: "verify",
|
|
25
|
+
namespace: "auth",
|
|
26
|
+
pre: ["UNAUTHENTICATED"],
|
|
27
|
+
post: ["PASSWORD_VERIFIED"],
|
|
28
|
+
patterns: [
|
|
29
|
+
["password", "check"],
|
|
30
|
+
["password", "compare"],
|
|
31
|
+
["password", "verify"],
|
|
32
|
+
["password", "auth"],
|
|
33
|
+
["passwd", "check"],
|
|
34
|
+
["pass", "check"],
|
|
35
|
+
["pass", "compare"],
|
|
36
|
+
["credential", "check"],
|
|
37
|
+
["credential", "compare"],
|
|
38
|
+
["secret", "check"],
|
|
39
|
+
["secret", "compare"],
|
|
40
|
+
["kbdint", "get"],
|
|
41
|
+
["otp", "check"],
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
// 登录完成(认证循环/命令处理器的完成点)——establish 原语
|
|
46
|
+
role: "establish",
|
|
47
|
+
namespace: "auth",
|
|
48
|
+
pre: [],
|
|
49
|
+
post: ["AUTHENTICATED"],
|
|
50
|
+
patterns: [
|
|
51
|
+
["authenticate"],
|
|
52
|
+
["login"],
|
|
53
|
+
["logon"],
|
|
54
|
+
["sign_in"],
|
|
55
|
+
["auth_success"],
|
|
56
|
+
["auth_ok"],
|
|
57
|
+
["handle", "pass"],
|
|
58
|
+
["do", "login"],
|
|
59
|
+
["do", "user"],
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
// 权限/会话守卫(权限词 × 检查动词,或 FTP/传输类命令处理器、
|
|
64
|
+
// 通道开启回调)——check_resource_ownership 同款语义(auth 命名空间,
|
|
65
|
+
// 见 REALWORLD_C_V6.md 发现 G5:跨命名空间 pre 不可满足)
|
|
66
|
+
role: "guard",
|
|
67
|
+
namespace: "auth",
|
|
68
|
+
pre: ["AUTHENTICATED"],
|
|
69
|
+
post: ["AUTHORIZED"],
|
|
70
|
+
patterns: [
|
|
71
|
+
["perm", "check"],
|
|
72
|
+
["authoriz", "check"],
|
|
73
|
+
["acl", "check"],
|
|
74
|
+
["grant", "check"],
|
|
75
|
+
["privilege", "check"],
|
|
76
|
+
["do", "retr"],
|
|
77
|
+
["do", "stor"],
|
|
78
|
+
["handle", "retr"],
|
|
79
|
+
["handle", "stor"],
|
|
80
|
+
["do", "download"],
|
|
81
|
+
["do", "upload"],
|
|
82
|
+
["do", "transfer"],
|
|
83
|
+
["start", "transfer"],
|
|
84
|
+
["send", "transfer"],
|
|
85
|
+
["open", "channel"],
|
|
86
|
+
["accept", "channel"],
|
|
87
|
+
["new", "channel"],
|
|
88
|
+
["new", "session", "channel"],
|
|
89
|
+
// 注意:不带 ["new","session"]——new_session 是会话工厂(创建而非检查),
|
|
90
|
+
// 实测(V7):守卫注解会误报其调用方(ftp_cb/tftp_cb/main 在登录前建会话)。
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
// 资源获取(打开文件/连接/日志)——open_file 同款语义
|
|
95
|
+
role: "open",
|
|
96
|
+
namespace: "file",
|
|
97
|
+
pre: [],
|
|
98
|
+
post: ["FILE_OPEN"],
|
|
99
|
+
patterns: [
|
|
100
|
+
["open", "file"],
|
|
101
|
+
["open", "data"],
|
|
102
|
+
["open", "connection"],
|
|
103
|
+
["open", "conn"],
|
|
104
|
+
["open", "log"],
|
|
105
|
+
["open", "db"],
|
|
106
|
+
["create", "connection"],
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
// 资源释放(关闭/销毁文件/连接)——close_file 同款语义
|
|
111
|
+
role: "close",
|
|
112
|
+
namespace: "file",
|
|
113
|
+
pre: ["FILE_OPEN"],
|
|
114
|
+
post: [],
|
|
115
|
+
invalidate: ["FILE_OPEN"],
|
|
116
|
+
patterns: [
|
|
117
|
+
["close", "file"],
|
|
118
|
+
["close", "data"],
|
|
119
|
+
["close", "connection"],
|
|
120
|
+
["close", "conn"],
|
|
121
|
+
["close", "log"],
|
|
122
|
+
["close", "db"],
|
|
123
|
+
["release", "connection"],
|
|
124
|
+
["destroy", "connection"],
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
/** 匹配命中数 ≥2 视为高置信(多个独立证据) */
|
|
129
|
+
const HIGH_CONFIDENCE_HITS = 2;
|
|
130
|
+
/**
|
|
131
|
+
* 为一组函数生成注解建议(确定性,同输入同输出)。
|
|
132
|
+
*
|
|
133
|
+
* @param functions — 项目函数列表(C IR)
|
|
134
|
+
* @param ruleNames — 已加载规则名集合(规则名函数按名即命中,无需注解建议)
|
|
135
|
+
* @param limit — 输出上限(默认 20,防大仓库刷屏;按置信度排序)
|
|
136
|
+
*/
|
|
137
|
+
function suggestAnnotations(functions, ruleNames, limit = 20) {
|
|
138
|
+
const suggestions = [];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
for (const fn of functions) {
|
|
141
|
+
if (!fn.name)
|
|
142
|
+
continue;
|
|
143
|
+
if (fn.protocol && (fn.protocol.pre_states?.length || fn.protocol.post_states?.length)) {
|
|
144
|
+
continue; // 已注解
|
|
145
|
+
}
|
|
146
|
+
if (ruleNames?.has(fn.name))
|
|
147
|
+
continue; // 规则名函数按名命中,无需注解
|
|
148
|
+
if (fn.external)
|
|
149
|
+
continue;
|
|
150
|
+
if (seen.has(fn.name))
|
|
151
|
+
continue;
|
|
152
|
+
seen.add(fn.name);
|
|
153
|
+
const lower = fn.name.toLowerCase();
|
|
154
|
+
for (const def of ROLE_DEFS) {
|
|
155
|
+
const hits = [];
|
|
156
|
+
for (const pattern of def.patterns) {
|
|
157
|
+
if (pattern.every((word) => lower.includes(word))) {
|
|
158
|
+
hits.push(pattern.join("+"));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (hits.length === 0)
|
|
162
|
+
continue;
|
|
163
|
+
const confidence = hits.length >= HIGH_CONFIDENCE_HITS ? "high" : "medium";
|
|
164
|
+
const invalidatePart = def.invalidate
|
|
165
|
+
? `, invalidate=${fmtStates(def.invalidate)}`
|
|
166
|
+
: "";
|
|
167
|
+
suggestions.push({
|
|
168
|
+
function: fn.name,
|
|
169
|
+
file: fn.file || "",
|
|
170
|
+
role: def.role,
|
|
171
|
+
namespace: def.namespace,
|
|
172
|
+
pre: def.pre,
|
|
173
|
+
post: def.post,
|
|
174
|
+
invalidate: def.invalidate,
|
|
175
|
+
confidence,
|
|
176
|
+
reasons: hits,
|
|
177
|
+
template: `/* @progmune(namespace="${def.namespace}", pre=${fmtStates(def.pre)}, post=${fmtStates(def.post)}${invalidatePart}) */`,
|
|
178
|
+
maskRisk: false, // 两遍计算(见下)
|
|
179
|
+
});
|
|
180
|
+
break; // 一个函数只取最先命中的角色
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// 第二遍:掩蔽风险——函数体调用了「已存在规则原语」或「本批同被建议的
|
|
184
|
+
// 函数」→ 注解后体内序列不再被验证,可能掩蔽其握有的违规(V7 实测:
|
|
185
|
+
// 植入流被 establish 注解变不透明后违规消失)。
|
|
186
|
+
const coSuggested = new Set(suggestions.map((s) => s.function));
|
|
187
|
+
for (const s of suggestions) {
|
|
188
|
+
const fnInfo = functions.find((f) => f.name === s.function);
|
|
189
|
+
s.maskRisk = (fnInfo?.calls || []).some((c) => (ruleNames?.has(c) ?? false) || coSuggested.has(c));
|
|
190
|
+
}
|
|
191
|
+
// 稳定性排序:置信度高优先,其次按 文件+名字(结果可复现)
|
|
192
|
+
const rank = { high: 0, medium: 1 };
|
|
193
|
+
suggestions.sort((a, b) => rank[a.confidence] - rank[b.confidence] ||
|
|
194
|
+
a.file.localeCompare(b.file) ||
|
|
195
|
+
a.function.localeCompare(b.function));
|
|
196
|
+
return suggestions.slice(0, limit);
|
|
197
|
+
}
|
|
198
|
+
function fmtStates(states) {
|
|
199
|
+
return `[${states.map((s) => `"${s}"`).join(", ")}]`;
|
|
200
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* annotation-suggest.test.ts — 注解建议引擎回归(纯函数,无文件系统 I/O)
|
|
5
|
+
*
|
|
6
|
+
* 词汇模式来自 3.7.6 金标 5/5 真实注解反推(REALWORLD_C_V6.md):
|
|
7
|
+
* check_user_pass/auth_password=verify、handle_PASS=establish、
|
|
8
|
+
* do_RETR/do_STOR/new_session_channel=guard、open/close_data_connection=资源生命周期。
|
|
9
|
+
*/
|
|
10
|
+
const vitest_1 = require("vitest");
|
|
11
|
+
const annotation_suggest_1 = require("./annotation-suggest");
|
|
12
|
+
const fn = (name, extra = {}) => ({
|
|
13
|
+
name,
|
|
14
|
+
file: "src/demo.c",
|
|
15
|
+
...extra,
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.describe)("annotation-suggest", () => {
|
|
18
|
+
(0, vitest_1.it)("verify:凭证名词 × 比对动词(check_user_pass / auth_password)", () => {
|
|
19
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([fn("check_user_pass"), fn("auth_password")]);
|
|
20
|
+
(0, vitest_1.expect)(s).toHaveLength(2);
|
|
21
|
+
for (const x of s) {
|
|
22
|
+
(0, vitest_1.expect)(x.role).toBe("verify");
|
|
23
|
+
(0, vitest_1.expect)(x.namespace).toBe("auth");
|
|
24
|
+
(0, vitest_1.expect)(x.pre).toEqual(["UNAUTHENTICATED"]);
|
|
25
|
+
(0, vitest_1.expect)(x.post).toEqual(["PASSWORD_VERIFIED"]);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.it)("establish:登录完成原语(handle_PASS / authenticate)", () => {
|
|
29
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([fn("handle_PASS"), fn("authenticate")]);
|
|
30
|
+
(0, vitest_1.expect)(s.map((x) => [x.function, x.role])).toEqual(vitest_1.expect.arrayContaining([
|
|
31
|
+
["handle_PASS", "establish"],
|
|
32
|
+
["authenticate", "establish"],
|
|
33
|
+
]));
|
|
34
|
+
(0, vitest_1.expect)(s[0].pre).toEqual([]);
|
|
35
|
+
(0, vitest_1.expect)(s[0].post).toEqual(["AUTHENTICATED"]);
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.it)("guard:FTP 命令处理器与通道开启回调(do_RETR/do_STOR/new_session_channel)", () => {
|
|
38
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([
|
|
39
|
+
fn("do_RETR"),
|
|
40
|
+
fn("do_STOR"),
|
|
41
|
+
fn("new_session_channel"),
|
|
42
|
+
]);
|
|
43
|
+
for (const x of s) {
|
|
44
|
+
(0, vitest_1.expect)(x.role).toBe("guard");
|
|
45
|
+
(0, vitest_1.expect)(x.pre).toEqual(["AUTHENTICATED"]);
|
|
46
|
+
(0, vitest_1.expect)(x.post).toEqual(["AUTHORIZED"]);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
(0, vitest_1.it)("资源生命周期:open/close_data_connection", () => {
|
|
50
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([
|
|
51
|
+
fn("open_data_connection"),
|
|
52
|
+
fn("close_data_connection"),
|
|
53
|
+
]);
|
|
54
|
+
const open = s.find((x) => x.function === "open_data_connection");
|
|
55
|
+
const close = s.find((x) => x.function === "close_data_connection");
|
|
56
|
+
(0, vitest_1.expect)(open?.role).toBe("open");
|
|
57
|
+
(0, vitest_1.expect)(open?.post).toEqual(["FILE_OPEN"]);
|
|
58
|
+
(0, vitest_1.expect)(close?.role).toBe("close");
|
|
59
|
+
(0, vitest_1.expect)(close?.pre).toEqual(["FILE_OPEN"]);
|
|
60
|
+
(0, vitest_1.expect)(close?.invalidate).toEqual(["FILE_OPEN"]);
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)("已注解函数不再建议", () => {
|
|
63
|
+
const annotated = fn("check_user_pass", {
|
|
64
|
+
protocol: { pre_states: ["UNAUTHENTICATED"], post_states: ["PASSWORD_VERIFIED"], namespace: "auth" },
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.expect)((0, annotation_suggest_1.suggestAnnotations)([annotated])).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.it)("规则名函数(按名即命中)不再建议", () => {
|
|
69
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([fn("verify_password")], new Set(["verify_password"]));
|
|
70
|
+
(0, vitest_1.expect)(s).toHaveLength(0);
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)("外部函数不再建议", () => {
|
|
73
|
+
(0, vitest_1.expect)((0, annotation_suggest_1.suggestAnnotations)([fn("check_password", { external: true })])).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
(0, vitest_1.it)("模板可直接粘贴:完整注释块文本", () => {
|
|
76
|
+
const [s] = (0, annotation_suggest_1.suggestAnnotations)([fn("check_user_pass")]);
|
|
77
|
+
(0, vitest_1.expect)(s.template).toBe('/* @progmune(namespace="auth", pre=["UNAUTHENTICATED"], post=["PASSWORD_VERIFIED"]) */');
|
|
78
|
+
});
|
|
79
|
+
(0, vitest_1.it)("确定性 + 上限:同输入同输出,超过 limit 截断(置信度优先)", () => {
|
|
80
|
+
const fns = Array.from({ length: 30 }, (_, i) => fn(`do_RETR_${i}`) // 全部 guard,单证据 medium
|
|
81
|
+
);
|
|
82
|
+
const a = (0, annotation_suggest_1.suggestAnnotations)(fns, undefined, 10);
|
|
83
|
+
const b = (0, annotation_suggest_1.suggestAnnotations)(fns, undefined, 10);
|
|
84
|
+
(0, vitest_1.expect)(a).toEqual(b);
|
|
85
|
+
(0, vitest_1.expect)(a).toHaveLength(10);
|
|
86
|
+
(0, vitest_1.expect)(a[0].function).toBe("do_RETR_0");
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)("无命中词汇的函数不产生建议", () => {
|
|
89
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([fn("parse_int"), fn("strlcat"), fn("main")]);
|
|
90
|
+
(0, vitest_1.expect)(s).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)("掩蔽风险:函数体调用已有规则原语 → maskRisk=true", () => {
|
|
93
|
+
const [s] = (0, annotation_suggest_1.suggestAnnotations)([fn("login_flow", { calls: ["verify_password"] })], new Set(["verify_password"]));
|
|
94
|
+
(0, vitest_1.expect)(s.maskRisk).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)("掩蔽风险:函数体调用本批同被建议的函数 → maskRisk=true(co-suggestion)", () => {
|
|
97
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([
|
|
98
|
+
fn("login_flow", { calls: ["start_file_transfer"] }),
|
|
99
|
+
fn("start_file_transfer"),
|
|
100
|
+
]);
|
|
101
|
+
const flow = s.find((x) => x.function === "login_flow");
|
|
102
|
+
(0, vitest_1.expect)(flow?.maskRisk).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)("叶子函数(体内无规则原语调用)→ maskRisk=false", () => {
|
|
105
|
+
const [s] = (0, annotation_suggest_1.suggestAnnotations)([fn("check_user_pass", { calls: ["strcmp", "strlcpy"] })]);
|
|
106
|
+
(0, vitest_1.expect)(s.maskRisk).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.it)("会话工厂 new_session 不再误判为守卫", () => {
|
|
109
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([fn("new_session")]);
|
|
110
|
+
(0, vitest_1.expect)(s).toHaveLength(0);
|
|
111
|
+
});
|
|
112
|
+
(0, vitest_1.it)("通道守卫 new_session_channel 仍命中 guard", () => {
|
|
113
|
+
const s = (0, annotation_suggest_1.suggestAnnotations)([fn("new_session_channel")]);
|
|
114
|
+
(0, vitest_1.expect)(s).toHaveLength(1);
|
|
115
|
+
(0, vitest_1.expect)(s[0].role).toBe("guard");
|
|
116
|
+
});
|
|
117
|
+
});
|
package/dist/call-sequence.js
CHANGED
|
@@ -151,7 +151,13 @@ function buildCallSequences(ir, keepNames, maxCalls = exports.MAX_SEQUENCE_CALLS
|
|
|
151
151
|
const calls = expandBody(f, 0, new Set());
|
|
152
152
|
if (calls.length === 0)
|
|
153
153
|
continue;
|
|
154
|
-
sequences.push({
|
|
154
|
+
sequences.push({
|
|
155
|
+
calls,
|
|
156
|
+
file: f.file,
|
|
157
|
+
function: f.name,
|
|
158
|
+
truncated: budget.truncated || undefined,
|
|
159
|
+
directCalls: f.calls || [],
|
|
160
|
+
});
|
|
155
161
|
}
|
|
156
162
|
return sequences;
|
|
157
163
|
}
|
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.7";
|
|
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
|
@@ -60,10 +60,24 @@ const confidence_calculator_1 = require("./confidence-calculator");
|
|
|
60
60
|
const violation_trace_1 = require("./violation-trace");
|
|
61
61
|
const api_semantic_mapper_1 = require("./api-semantic-mapper");
|
|
62
62
|
const protocol_domain_validator_1 = require("./protocol-domain-validator");
|
|
63
|
+
const annotation_suggest_1 = require("../annotation-suggest");
|
|
63
64
|
const call_graph_propagator_1 = require("./call-graph-propagator");
|
|
64
65
|
const ssg_bridge_1 = require("./ssg-bridge");
|
|
65
66
|
const call_sequence_1 = require("../call-sequence");
|
|
67
|
+
const extract_ir_1 = require("../extract-ir");
|
|
68
|
+
const extract_ir_python_1 = require("../extract-ir-python");
|
|
69
|
+
const extract_ir_c_1 = require("../extract-ir-c");
|
|
66
70
|
// ── Main Entry Point ──
|
|
71
|
+
/**
|
|
72
|
+
* Trust 决策主入口:收集 → 归一化 → 评分 → 决策 → 组装。
|
|
73
|
+
*
|
|
74
|
+
* IR 写盘语义(勿改):项目缺少 ir.json 时,引擎按 ctx.language 自动提取
|
|
75
|
+
* 并落盘(TS/JS → extractIR 裸数组;Python → extractIRPython 裸数组;
|
|
76
|
+
* C → extractIRC 合并形态 { typeMap, functions })——注解合并(P4.5)依赖
|
|
77
|
+
* ir.json,调用方【无需】手动 extractProjectIR/写盘(曾是对 C 注解静默
|
|
78
|
+
* 失效的文档/API 陷阱,回归测试见 tests/trust/engine.test.ts「DSH 陷阱」)。
|
|
79
|
+
* 若项目已有 ir.json,以现有文件为准(不覆盖)。
|
|
80
|
+
*/
|
|
67
81
|
async function evaluateTrust(ctx) {
|
|
68
82
|
const engineVersion = "trust-runtime-v1.0.0";
|
|
69
83
|
const timestamp = new Date().toISOString();
|
|
@@ -74,7 +88,7 @@ async function evaluateTrust(ctx) {
|
|
|
74
88
|
// ── Phase 5: Build call graph for cross-function propagation ──
|
|
75
89
|
const callGraph = (0, call_graph_propagator_1.buildCallGraphFromIR)(path.join(ctx.projectPath, "ir.json"));
|
|
76
90
|
const enterpriseViolations = collectEnterpriseViolations(ctx);
|
|
77
|
-
const { violations: protocolViolations, coverage: mappingCoverageData, ssgCoverage: ssgCov } = await collectProtocolViolations(ctx, callGraph);
|
|
91
|
+
const { violations: protocolViolations, coverage: mappingCoverageData, ssgCoverage: ssgCov, annotationSuggestions: cAnnotationSuggestions } = await collectProtocolViolations(ctx, callGraph);
|
|
78
92
|
const expressResult = collectExpressViolations(ctx);
|
|
79
93
|
const nestjsResult = collectNestJSViolations(ctx);
|
|
80
94
|
const trpcResult = collectTRPCViolations(ctx);
|
|
@@ -161,6 +175,7 @@ async function evaluateTrust(ctx) {
|
|
|
161
175
|
commit: ctx.commit,
|
|
162
176
|
timestamp,
|
|
163
177
|
engineVersion,
|
|
178
|
+
annotationSuggestions: cAnnotationSuggestions,
|
|
164
179
|
overall: {
|
|
165
180
|
score: effectiveScore,
|
|
166
181
|
decision,
|
|
@@ -704,21 +719,39 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
704
719
|
let ssgTotalCalls = 0;
|
|
705
720
|
let ssgMatchedCalls = 0;
|
|
706
721
|
let ssgViolationCount = 0;
|
|
722
|
+
// C 注解建议(函数级作用域——返回值在 try 外组装)
|
|
723
|
+
let annotationSuggestions;
|
|
707
724
|
try {
|
|
708
725
|
// ── P4.5 校准:TS/JS 项目在 ir.json 缺失时先提取 IR ──
|
|
709
726
|
// 序列必须来自「函数体内真实调用」而非「文件内函数声明名单」——
|
|
710
727
|
// 声明顺序 ≠ 执行顺序:正则扫描会把声明当调用(auth.ts 误报),
|
|
711
728
|
// 也会因调用数不足阈值漏掉单调用违规文件(bad_flow 漏报)。
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
729
|
+
// 注解合并(P4.5)依赖 ir.json 写盘:此前仅 TS/JS 自动提取,C/Python 项目
|
|
730
|
+
// 直接调 evaluateTrust 时注解静默失效(DSH 复测发现的文档/API 陷阱)——
|
|
731
|
+
// 按语言分派提取器兜底写盘;TS/JS 路径保持原样(裸数组形态,零变化)。
|
|
732
|
+
{
|
|
733
|
+
const lang = ctx.language || "typescript";
|
|
734
|
+
// 静态导入(vitest 环境下 lazy require 的 CJS 互操作不可靠——
|
|
735
|
+
// TS 路径从未触发过该分支:仓库根遗留 ir.json 短路了 existsSync 检查)
|
|
736
|
+
const autoExtractor = {
|
|
737
|
+
typescript: () => (0, extract_ir_1.extractIR)(ctx.projectPath),
|
|
738
|
+
javascript: () => (0, extract_ir_1.extractIR)(ctx.projectPath),
|
|
739
|
+
python: () => (0, extract_ir_python_1.extractIRPython)(ctx.projectPath),
|
|
740
|
+
c: () => (0, extract_ir_c_1.extractIRC)(ctx.projectPath),
|
|
741
|
+
};
|
|
742
|
+
const extractFn = autoExtractor[lang];
|
|
743
|
+
if (extractFn) {
|
|
744
|
+
try {
|
|
745
|
+
const fs = require("fs");
|
|
746
|
+
if (!fs.existsSync(path.join(ctx.projectPath, "ir.json"))) {
|
|
747
|
+
const ir = extractFn();
|
|
748
|
+
// C 走合并形态(与 execute/MCP 写盘一致);TS/Python 保持裸数组形态
|
|
749
|
+
const payload = lang === "c" ? { typeMap: {}, functions: ir } : ir;
|
|
750
|
+
fs.writeFileSync(path.join(ctx.projectPath, "ir.json"), JSON.stringify(payload, null, 2));
|
|
751
|
+
}
|
|
719
752
|
}
|
|
753
|
+
catch { /* best-effort — 回退正则扫描 */ }
|
|
720
754
|
}
|
|
721
|
-
catch { /* best-effort — 回退正则扫描 */ }
|
|
722
755
|
}
|
|
723
756
|
// ── SSG State Machine: load protocol rules once ──
|
|
724
757
|
protocolRulesData = (0, ssg_bridge_1.loadProtocolRules)(ctx.projectPath);
|
|
@@ -772,6 +805,10 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
772
805
|
// ── P4.6.1: 词段匹配门控的项目函数集合(best-effort,与注解合并共用 ir.json) ──
|
|
773
806
|
// 词段匹配只对项目函数适用(改名协议原语);外部库调用走 alias/关键词桥接。
|
|
774
807
|
let projectFunctions;
|
|
808
|
+
// ── 注解建议(采纳生死线):未注解 C 项目的原语注解候选(加性,仅 C) ──
|
|
809
|
+
// 未注解项目 SSG 层静默(0 flags)——建议清单是「看不见的问题」的入口:
|
|
810
|
+
// 按函数名词汇启发式给出现成注释块模板(角色/命名空间/状态转移预填),
|
|
811
|
+
// 人工确认后生效(与 c-alias-propose 的确认门同一哲学)。
|
|
775
812
|
try {
|
|
776
813
|
const fs = require("fs");
|
|
777
814
|
const irPath = path.join(ctx.projectPath, "ir.json");
|
|
@@ -779,6 +816,9 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
779
816
|
const ir = JSON.parse(fs.readFileSync(irPath, "utf-8"));
|
|
780
817
|
const functions = Array.isArray(ir) ? ir : (ir.functions || []);
|
|
781
818
|
projectFunctions = (0, call_sequence_1.collectProjectFunctionNames)(functions);
|
|
819
|
+
if ((ctx.language || "typescript") === "c") {
|
|
820
|
+
annotationSuggestions = (0, annotation_suggest_1.suggestAnnotations)(functions, protocolRulesData ? new Set(protocolRulesData.rules.keys()) : undefined);
|
|
821
|
+
}
|
|
782
822
|
}
|
|
783
823
|
}
|
|
784
824
|
catch { /* best-effort */ }
|
|
@@ -850,7 +890,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
850
890
|
// A sequence may be a valid protocol operation but still violate
|
|
851
891
|
// specific security requirements (e.g., TLS without cert verify)
|
|
852
892
|
try {
|
|
853
|
-
const specificViolations = (0, protocol_domain_validator_1.checkSpecificViolations)(semantic, seq.file ? path.join(ctx.projectPath, seq.file) : undefined);
|
|
893
|
+
const specificViolations = (0, protocol_domain_validator_1.checkSpecificViolations)(semantic, seq.file ? path.join(ctx.projectPath, seq.file) : undefined, ctx.language);
|
|
854
894
|
for (const sv of specificViolations) {
|
|
855
895
|
violations.push({
|
|
856
896
|
severity: "medium", // Phase 2: per-function window has inherent limitations
|
|
@@ -873,7 +913,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
873
913
|
// to bridge real API names to abstract protocol function names.
|
|
874
914
|
if (protocolRulesData) {
|
|
875
915
|
try {
|
|
876
|
-
const ssgResult = (0, ssg_bridge_1.validateSequenceWithSSG)(semantic.steps, protocolRulesData.rules, protocolRulesData.namespaceInitialStates, seq.file, protocolRulesData.aliasIndex, protocolRulesData.wildcardAliases, projectFunctions);
|
|
916
|
+
const ssgResult = (0, ssg_bridge_1.validateSequenceWithSSG)(semantic.steps, protocolRulesData.rules, protocolRulesData.namespaceInitialStates, seq.file, protocolRulesData.aliasIndex, protocolRulesData.wildcardAliases, projectFunctions, seq.directCalls ? new Set(seq.directCalls) : undefined);
|
|
877
917
|
ssgResults.push(ssgResult);
|
|
878
918
|
ssgTotalCalls += ssgResult.stats.totalCalls;
|
|
879
919
|
ssgMatchedCalls += ssgResult.stats.matchedCalls;
|
|
@@ -933,6 +973,7 @@ async function collectProtocolViolations(ctx, callGraph) {
|
|
|
933
973
|
aliasWarnings: protocolRulesData.aliasWarnings?.length
|
|
934
974
|
? protocolRulesData.aliasWarnings : undefined,
|
|
935
975
|
} : undefined,
|
|
976
|
+
annotationSuggestions,
|
|
936
977
|
};
|
|
937
978
|
}
|
|
938
979
|
function extractCallSequencesFromProject(projectPath, language, keepNames) {
|
|
@@ -284,6 +284,10 @@ exports.SPECIFIC_VIOLATION_CHECKS = [
|
|
|
284
284
|
ruleId: "PLAINTEXT_AUTH_WITHOUT_TLS",
|
|
285
285
|
description: "Authentication credentials transmitted without TLS protection. " +
|
|
286
286
|
"SASL PLAIN, LOGIN, or credential data must be sent over an encrypted channel.",
|
|
287
|
+
// C 排除(2026-08-28,真实语料证据):libssh 演示 1 FP(SSH 明文协议被
|
|
288
|
+
// Web/TLS 语义误映射)+ uftpd 采纳案例 2 FP(handle_USER/main)——FTP/SSH
|
|
289
|
+
// 应用层本就明文,传输加密在协议自身/进程外,本规则的 Web 语义对 C 无 TP。
|
|
290
|
+
languages: ["typescript", "javascript", "python"],
|
|
287
291
|
check: (steps) => {
|
|
288
292
|
// Auth data being prepared (credentials + mechanism)
|
|
289
293
|
const hasAuthCred = steps.some((s) => s.domain === "auth_cred");
|
|
@@ -649,10 +653,16 @@ exports.SPECIFIC_VIOLATION_CHECKS = [
|
|
|
649
653
|
/**
|
|
650
654
|
* Run specific violation checks against a semantic sequence.
|
|
651
655
|
* Returns violations found (empty for Phase 1 MVP).
|
|
656
|
+
*
|
|
657
|
+
* @param language — project language ("c" | "python" | "typescript" | …);
|
|
658
|
+
* checks with a `languages` allowlist are skipped for other languages.
|
|
652
659
|
*/
|
|
653
|
-
function checkSpecificViolations(seq, _filePath) {
|
|
660
|
+
function checkSpecificViolations(seq, _filePath, language) {
|
|
654
661
|
const violations = [];
|
|
655
662
|
for (const check of exports.SPECIFIC_VIOLATION_CHECKS) {
|
|
663
|
+
if (check.languages && language && !check.languages.includes(language)) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
656
666
|
if (check.check(seq.steps, _filePath)) {
|
|
657
667
|
violations.push({
|
|
658
668
|
ruleId: check.ruleId,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* protocol-domain-validator.test.ts — specific violation checks 的语言门控回归
|
|
5
|
+
*
|
|
6
|
+
* 2026-08-28 语言门控(REALWORLD_C_V6.md):PLAINTEXT_AUTH_WITHOUT_TLS 的
|
|
7
|
+
* Web/TLS 语义在 C 真实项目上 3 FP / 0 TP(libssh 演示 1 + uftpd 采纳案例 2,
|
|
8
|
+
* FTP/SSH 应用层本就明文)。SSH 主机密钥规则保留全语言(libssh 演示 1 TP)。
|
|
9
|
+
*
|
|
10
|
+
* 测试避免文件系统 I/O——直接构造 SemanticSequence。
|
|
11
|
+
*/
|
|
12
|
+
const vitest_1 = require("vitest");
|
|
13
|
+
const protocol_domain_validator_1 = require("./protocol-domain-validator");
|
|
14
|
+
/** PLAINTEXT 触发序列:auth 凭证 + 网络发送 + 无 TLS 保护 */
|
|
15
|
+
function plaintextSequence() {
|
|
16
|
+
return {
|
|
17
|
+
steps: [
|
|
18
|
+
{ api: "create_credentials", domain: "auth_cred", description: "", source: "lookup" },
|
|
19
|
+
{ api: "network_send", domain: "conn_mgmt", description: "", source: "lookup" },
|
|
20
|
+
],
|
|
21
|
+
domains: ["auth_cred", "conn_mgmt"],
|
|
22
|
+
primaryDomain: "conn_mgmt",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** SSH 无主机密钥校验触发序列:≥4 个 ssh_ops + userauth + 凭证、无 hostkey */
|
|
26
|
+
function sshNoHostKeySequence() {
|
|
27
|
+
return {
|
|
28
|
+
steps: [
|
|
29
|
+
{ api: "ssh_bind_accept", domain: "ssh_ops", description: "", source: "lookup" },
|
|
30
|
+
{ api: "ssh_handle_key_exchange", domain: "ssh_ops", description: "", source: "lookup" },
|
|
31
|
+
{ api: "ssh_userauth_password", domain: "ssh_ops", description: "", source: "lookup" },
|
|
32
|
+
{ api: "ssh_event_dopoll", domain: "ssh_ops", description: "", source: "lookup" },
|
|
33
|
+
{ api: "password_compare", domain: "auth_cred", description: "", source: "lookup" },
|
|
34
|
+
],
|
|
35
|
+
domains: ["ssh_ops", "auth_cred"],
|
|
36
|
+
primaryDomain: "ssh_ops",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
(0, vitest_1.describe)("protocol-domain-validator language gating", () => {
|
|
40
|
+
(0, vitest_1.it)("PLAINTEXT_AUTH_WITHOUT_TLS fires for typescript", () => {
|
|
41
|
+
const v = (0, protocol_domain_validator_1.checkSpecificViolations)(plaintextSequence(), undefined, "typescript");
|
|
42
|
+
(0, vitest_1.expect)(v.map((x) => x.ruleId)).toContain("PLAINTEXT_AUTH_WITHOUT_TLS");
|
|
43
|
+
});
|
|
44
|
+
(0, vitest_1.it)("PLAINTEXT_AUTH_WITHOUT_TLS fires for python", () => {
|
|
45
|
+
const v = (0, protocol_domain_validator_1.checkSpecificViolations)(plaintextSequence(), undefined, "python");
|
|
46
|
+
(0, vitest_1.expect)(v.map((x) => x.ruleId)).toContain("PLAINTEXT_AUTH_WITHOUT_TLS");
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.it)("PLAINTEXT_AUTH_WITHOUT_TLS does NOT fire for C (language gate)", () => {
|
|
49
|
+
const v = (0, protocol_domain_validator_1.checkSpecificViolations)(plaintextSequence(), undefined, "c");
|
|
50
|
+
(0, vitest_1.expect)(v.map((x) => x.ruleId)).not.toContain("PLAINTEXT_AUTH_WITHOUT_TLS");
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)("SSH_NO_HOST_KEY_CHECK still fires for C (ungated — libssh TP 保留)", () => {
|
|
53
|
+
const v = (0, protocol_domain_validator_1.checkSpecificViolations)(sshNoHostKeySequence(), undefined, "c");
|
|
54
|
+
(0, vitest_1.expect)(v.map((x) => x.ruleId)).toContain("SSH_NO_HOST_KEY_CHECK");
|
|
55
|
+
});
|
|
56
|
+
(0, vitest_1.it)("undefined language keeps legacy behavior (all checks run)", () => {
|
|
57
|
+
const v = (0, protocol_domain_validator_1.checkSpecificViolations)(plaintextSequence());
|
|
58
|
+
(0, vitest_1.expect)(v.map((x) => x.ruleId)).toContain("PLAINTEXT_AUTH_WITHOUT_TLS");
|
|
59
|
+
});
|
|
60
|
+
});
|
package/dist/trust/ssg-bridge.js
CHANGED
|
@@ -251,29 +251,32 @@ function inferRuleName(apiName, domain, description, ruleNames, namespace, alias
|
|
|
251
251
|
if (ruleName)
|
|
252
252
|
return ruleName;
|
|
253
253
|
}
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return ruleName;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// Strategy 2: Word-segment match — only match on complete word boundaries.
|
|
261
|
-
// Split both the rule name and normalized call name into word segments
|
|
262
|
-
// (separated by _). Match if every word in the rule name appears as a
|
|
263
|
-
// word segment in the normalized call name. This prevents "status" from
|
|
264
|
-
// matching "poll_status" via raw substring, since "status" must appear
|
|
265
|
-
// as a complete _-delimited segment.
|
|
266
|
-
//
|
|
267
|
-
// 门控(P4.6.1):词段匹配只对「项目函数」适用——它是为改名协议原语设计的
|
|
268
|
-
// (协议原语必然是项目内函数,如 S5 的 create_active_session)。外部库调用
|
|
269
|
-
// (如 Node 的 readFileSync)经词段撞上 read_file 是纯噪声:外部 API 的语义
|
|
270
|
-
// 桥接走 alias 配置(Strategy 0b)或 domain 关键词(Strategy 3),不走词段。
|
|
254
|
+
// 门控判定(P4.6.1 词段门控同款):项目函数才能按规范化/词段形态命中——
|
|
255
|
+
// 外部 API(如 Windows 的 ReadFile → snake_case 撞上 read_file)是纯噪声,
|
|
256
|
+
// 其语义桥接走 alias 配置(Strategy 0b)或 domain 关键词(Strategy 3)。
|
|
271
257
|
// 未提供 projectFunctions 时保持旧行为(向后兼容测试与无 IR 的调用方)。
|
|
272
258
|
const isProjectFn = !projectFunctions
|
|
273
259
|
|| projectFunctions.has(apiName)
|
|
274
260
|
|| projectFunctions.has(lowerApi)
|
|
275
261
|
|| projectFunctions.has(normalized)
|
|
276
262
|
|| (dotIdx >= 0 && projectFunctions.has(lowerApi.slice(dotIdx + 1)));
|
|
263
|
+
// Strategy 1: Exact match against rule names.
|
|
264
|
+
// 原始名(含小写)精确匹配不限门控;规范化形态(CamelCase → snake_case)
|
|
265
|
+
// 仅项目函数适用——真实 C 语料验证中 ReadFile/WriteFile/DeleteFile 经
|
|
266
|
+
// normalized 撞上 read_file/write_file/delete_file 是 11/24 FP 的主导源,
|
|
267
|
+
// 而注解桥接(ACLCheckAllPerm → acl_check_all_perm)全是项目函数,不受影响。
|
|
268
|
+
for (const ruleName of ruleNames) {
|
|
269
|
+
if (lowerApi === ruleName) {
|
|
270
|
+
return ruleName;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (isProjectFn) {
|
|
274
|
+
for (const ruleName of ruleNames) {
|
|
275
|
+
if (normalized === ruleName) {
|
|
276
|
+
return ruleName;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
277
280
|
for (const ruleName of ruleNames) {
|
|
278
281
|
if (projectFunctions && !isProjectFn)
|
|
279
282
|
continue;
|
|
@@ -319,7 +322,12 @@ function inferRuleName(apiName, domain, description, ruleNames, namespace, alias
|
|
|
319
322
|
*/
|
|
320
323
|
function validateSequenceWithSSG(steps, rules, namespaceInitialStates, file, aliasIndex, wildcardAliases,
|
|
321
324
|
/** 项目函数名集合(含裸名/全名/小写变体由调用方构造)——提供后词段匹配只对项目函数适用 */
|
|
322
|
-
projectFunctions
|
|
325
|
+
projectFunctions,
|
|
326
|
+
/** 入口函数的直接调用集合——endState 检查的资源获取溯源:经内联 helper
|
|
327
|
+
* 获取的资源不归因给入口(nginx 回调式生命周期:open 在 helper 链内、
|
|
328
|
+
* close 在回调里,直接调用序列看不到),提供后仅在获取调用 ∈ 直接调用
|
|
329
|
+
* 时报告 endState。未提供时保持旧行为。 */
|
|
330
|
+
entryDirectCalls) {
|
|
323
331
|
const allRuleNames = Array.from(rules.keys());
|
|
324
332
|
const aliases = aliasIndex || new Map();
|
|
325
333
|
const wildcards = wildcardAliases || new Map();
|
|
@@ -359,14 +367,15 @@ projectFunctions) {
|
|
|
359
367
|
}
|
|
360
368
|
})();
|
|
361
369
|
// 本序列中"新获取"的资源状态(endState 检查只针对本序列获取、未释放的状态——
|
|
362
|
-
// 继承自命名空间初始状态的不算泄漏,与 planner
|
|
363
|
-
|
|
364
|
-
const
|
|
370
|
+
// 继承自命名空间初始状态的不算泄漏,与 planner 语义一致)。
|
|
371
|
+
// 记录获取调用名:endState 归因溯源(入口直接调用 vs 内联 helper 获取)。
|
|
372
|
+
const acquiredStates = new Map(); // `${ns}::${state}` → 获取调用名
|
|
373
|
+
const trackAcquiredStates = (before, after, acquiringCall) => {
|
|
365
374
|
for (const ns of Object.keys(after)) {
|
|
366
375
|
const prev = before?.[ns] || [];
|
|
367
376
|
for (const s of after[ns]) {
|
|
368
377
|
if (!prev.includes(s))
|
|
369
|
-
acquiredStates.
|
|
378
|
+
acquiredStates.set(`${ns}::${s}`, acquiringCall);
|
|
370
379
|
}
|
|
371
380
|
}
|
|
372
381
|
};
|
|
@@ -413,7 +422,7 @@ projectFunctions) {
|
|
|
413
422
|
// Advance state
|
|
414
423
|
ctx.ledger.push(result.transition);
|
|
415
424
|
ctx.currentState = result.transition.statesAfter;
|
|
416
|
-
trackAcquiredStates(result.transition.statesBefore, result.transition.statesAfter);
|
|
425
|
+
trackAcquiredStates(result.transition.statesBefore, result.transition.statesAfter, step.api);
|
|
417
426
|
}
|
|
418
427
|
else {
|
|
419
428
|
violatedCalls++;
|
|
@@ -445,7 +454,7 @@ projectFunctions) {
|
|
|
445
454
|
// Still advance state on rejection (best-effort: apply transition anyway
|
|
446
455
|
// so subsequent calls can be validated)
|
|
447
456
|
ctx.ledger.push(result.transition);
|
|
448
|
-
trackAcquiredStates(result.transition.statesBefore, result.transition.statesAfter);
|
|
457
|
+
trackAcquiredStates(result.transition.statesBefore, result.transition.statesAfter, step.api);
|
|
449
458
|
}
|
|
450
459
|
}
|
|
451
460
|
catch {
|
|
@@ -479,7 +488,12 @@ projectFunctions) {
|
|
|
479
488
|
const ctx = contexts.get(hs.namespace);
|
|
480
489
|
if (!ctx)
|
|
481
490
|
continue;
|
|
482
|
-
|
|
491
|
+
const acquiringCall = acquiredStates.get(`${hs.namespace}::${hs.state}`);
|
|
492
|
+
if (!acquiringCall)
|
|
493
|
+
continue;
|
|
494
|
+
// 资源获取溯源:经内联 helper 获取的状态不归因给入口(释放可能存在于
|
|
495
|
+
// 兄弟 helper 或回调注册中——nginx 回调式生命周期是 12/24 FP 的主源)
|
|
496
|
+
if (entryDirectCalls && !entryDirectCalls.has(acquiringCall))
|
|
483
497
|
continue;
|
|
484
498
|
const cur = ctx.currentState[hs.namespace] || [];
|
|
485
499
|
if (!cur.includes(hs.state))
|
|
@@ -678,6 +692,32 @@ function loadProtocolRules(projectPath) {
|
|
|
678
692
|
nsInit._global = "INIT";
|
|
679
693
|
if (!nsInit.stateless)
|
|
680
694
|
nsInit.stateless = "IDLE";
|
|
695
|
+
// ── Load shared C alias registry (c-aliases.json, confirmed entries only) ──
|
|
696
|
+
// 注解驱动定位的孵化器燃料:库边界别名跨项目迁移——用户项目别名回写提案、
|
|
697
|
+
// 人工确认(status=confirmed)后对全部项目生效。加载顺序在项目别名之前:
|
|
698
|
+
// 项目本地映射优先于共享表(与「项目别名不覆盖全局」同哲学,first-wins)。
|
|
699
|
+
try {
|
|
700
|
+
const cRegistryPath = [
|
|
701
|
+
projectPath ? path.join(projectPath, "c-aliases.json") : "",
|
|
702
|
+
path.join(process.cwd(), "c-aliases.json"),
|
|
703
|
+
path.join(__dirname, "..", "c-aliases.json"),
|
|
704
|
+
].find((p) => p && fs.existsSync(p));
|
|
705
|
+
if (cRegistryPath) {
|
|
706
|
+
const reg = JSON.parse(fs.readFileSync(cRegistryPath, "utf-8"));
|
|
707
|
+
for (const entry of reg.entries || []) {
|
|
708
|
+
if (entry.status !== "confirmed")
|
|
709
|
+
continue;
|
|
710
|
+
const callName = String(entry.call).toLowerCase().trim();
|
|
711
|
+
const ruleName = String(entry.rule);
|
|
712
|
+
if (!callName || !rules.has(ruleName))
|
|
713
|
+
continue;
|
|
714
|
+
if (!aliasIndex.has(callName)) {
|
|
715
|
+
aliasIndex.set(callName, ruleName);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch { /* best-effort — 共享表缺失或损坏不影响验证 */ }
|
|
681
721
|
// ── Load project-level aliases (supplemental, never override global) ──
|
|
682
722
|
let projectAliases;
|
|
683
723
|
let aliasWarnings;
|
|
@@ -8,8 +8,44 @@
|
|
|
8
8
|
* 3. Detects state machine violations
|
|
9
9
|
* 4. Passes valid sequences without false positives
|
|
10
10
|
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
11
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
45
|
const vitest_1 = require("vitest");
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
13
49
|
const ssg_bridge_1 = require("./ssg-bridge");
|
|
14
50
|
// Load protocol rules once for all tests
|
|
15
51
|
const protoData = (0, ssg_bridge_1.loadProtocolRules)();
|
|
@@ -272,3 +308,43 @@ function step(api, domain, description = "") {
|
|
|
272
308
|
});
|
|
273
309
|
});
|
|
274
310
|
});
|
|
311
|
+
(0, vitest_1.describe)("共享 C 别名表(c-aliases.json,孵化器机制)", () => {
|
|
312
|
+
(0, vitest_1.it)("confirmed 条目被加载进 aliasIndex;proposed 条目不加载(人工确认门)", () => {
|
|
313
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-calias-"));
|
|
314
|
+
try {
|
|
315
|
+
fs.writeFileSync(path.join(dir, "c-aliases.json"), JSON.stringify({
|
|
316
|
+
entries: [
|
|
317
|
+
{ call: "ssh_userauth_password", rule: "verify_password", status: "confirmed" },
|
|
318
|
+
{ call: "some_lib_connect", rule: "connect_db", status: "proposed" },
|
|
319
|
+
{ call: "ghost_call", rule: "no_such_rule", status: "confirmed" },
|
|
320
|
+
],
|
|
321
|
+
}));
|
|
322
|
+
const loaded = (0, ssg_bridge_1.loadProtocolRules)(dir);
|
|
323
|
+
(0, vitest_1.expect)(loaded).not.toBeNull();
|
|
324
|
+
(0, vitest_1.expect)(loaded.aliasIndex.get("ssh_userauth_password")).toBe("verify_password");
|
|
325
|
+
(0, vitest_1.expect)(loaded.aliasIndex.has("some_lib_connect")).toBe(false); // 未确认不生效
|
|
326
|
+
(0, vitest_1.expect)(loaded.aliasIndex.has("ghost_call")).toBe(false); // 规则不存在跳过
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
(0, vitest_1.it)("共享表不覆盖全局别名与项目别名(first-wins)", () => {
|
|
333
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-calias2-"));
|
|
334
|
+
try {
|
|
335
|
+
// 全局 verify_hash 的别名 jsonwebtoken.verify——共享表同名条目不得覆盖
|
|
336
|
+
fs.writeFileSync(path.join(dir, "c-aliases.json"), JSON.stringify({
|
|
337
|
+
entries: [
|
|
338
|
+
{ call: "jsonwebtoken.verify", rule: "verify_password", status: "confirmed" },
|
|
339
|
+
],
|
|
340
|
+
}));
|
|
341
|
+
const loaded = (0, ssg_bridge_1.loadProtocolRules)(dir);
|
|
342
|
+
const globalRule = loaded.rules.get("verify_token")?.aliases?.includes("jsonwebtoken.verify");
|
|
343
|
+
// jsonwebtoken.verify 若已存在全局别名,共享表不覆盖(值保持原规则)
|
|
344
|
+
(0, vitest_1.expect)(loaded.aliasIndex.get("jsonwebtoken.verify")).not.toBe("verify_password");
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
本文档面向两类读者:想快速理解的初学者,与需要专业判断的投资人。</div>
|
|
175
175
|
<div class="meta">
|
|
176
176
|
<span>📄 版本 v1.0 · 2026-08</span>
|
|
177
|
-
<span>🧪 引擎版本
|
|
177
|
+
<span>🧪 引擎版本 v<span id="npmVersion">3.7.6</span></span>
|
|
178
178
|
<span>📊 13/13 安全类别覆盖</span>
|
|
179
179
|
<span>🔓 开源 MIT</span>
|
|
180
180
|
<span>📦 npm install progmune-runtime</span>
|
|
@@ -724,5 +724,24 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
724
724
|
</div>
|
|
725
725
|
|
|
726
726
|
</div>
|
|
727
|
+
|
|
728
|
+
<script>
|
|
729
|
+
// ═══ 引擎版本号动态展示:registry.npmjs.org dist-tags.latest ═══
|
|
730
|
+
// 发布到 npm 后自动跟随最新版本;拉取失败保留静态回退值。
|
|
731
|
+
(async function loadNpmVersion() {
|
|
732
|
+
const el = document.getElementById("npmVersion");
|
|
733
|
+
if (!el) return;
|
|
734
|
+
const ctrl = new AbortController();
|
|
735
|
+
const timer = setTimeout(() => ctrl.abort(), 8000);
|
|
736
|
+
try {
|
|
737
|
+
const res = await fetch("https://registry.npmjs.org/progmune-runtime", { signal: ctrl.signal });
|
|
738
|
+
clearTimeout(timer);
|
|
739
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
740
|
+
const pkg = await res.json();
|
|
741
|
+
const latest = pkg && pkg["dist-tags"] && pkg["dist-tags"].latest;
|
|
742
|
+
if (latest) el.textContent = latest;
|
|
743
|
+
} catch { /* 保留静态回退值 */ }
|
|
744
|
+
})();
|
|
745
|
+
</script>
|
|
727
746
|
</body>
|
|
728
747
|
</html>
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.7",
|
|
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/",
|
|
7
7
|
"protocols.json",
|
|
8
|
+
"c-aliases.json",
|
|
8
9
|
"CHANGELOG.md",
|
|
9
10
|
"docs/Progmune_项目全解.html",
|
|
10
11
|
"docs/Progmune_投资人白皮书_v2.0.html"
|