progmune-runtime 3.7.3 → 3.7.5
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 +45 -0
- package/LICENSE +21 -0
- package/README.md +4 -4
- package/README.zh-CN.md +4 -4
- package/dist/call-sequence.js +59 -15
- package/dist/call-sequence.test.js +139 -0
- package/dist/extract-ir-c.js +727 -0
- package/dist/extract-ir-c.test.js +438 -0
- package/dist/extract-project-ir.js +7 -0
- package/dist/extract-project-ir.test.js +87 -0
- package/dist/failure-collector.js +5 -1
- package/dist/sdk.js +1 -1
- package/dist/ssg-validator.js +7 -0
- package/dist/trust/engine.js +33 -18
- package/dist/trust/ssg-bridge.js +7 -2
- package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.5] — 2026-08-27
|
|
4
|
+
|
|
5
|
+
### C 真实语料验证 + 注解驱动演示 + 引擎修复(DSH 双轮评审合入)
|
|
6
|
+
|
|
7
|
+
- **修复(DSH):单行指针返回函数系统性漏提取**——`char *foo(`/`SSL *foo(` 类定义被 `(?:^|\s)` 锚点漏掉(名字前是 `*` 非空白);改为 lookbehind `(?<![a-zA-Z0-9_])` + 回归测试。openssl 等指针密集型仓库补回 ~1,500 函数(如 openssl 15,539 含指针修复与表面过滤的净效果)
|
|
8
|
+
- **修复:P4.6 展开兆级序列**(DSH 实测 openssl 单序列可达 1M+ 调用、全扫描 15–25 分钟)——`buildCallSequences` 预算制展开(`MAX_SEQUENCE_CALLS=2000` 可注入,入口自身调用按序优先、超预算即停,`CallSequence.truncated` 标记,不去重——重复调用对状态机有语义);openssl 全扫描 **15–25 分钟 → 222s**。零漂移前置实测:Python 盲测语料最大序列 23、TS 自身 IR 最大 824(预算零影响),Python 盲测复跑 64 违规不变。**截断是诚实的召回边界**(超大序列尾部违规不可见,非静默回归)
|
|
9
|
+
- **真实语料四仓库**(libssh/redis/nginx/openssl):24 flags 全部人工标注 FP(0 TP)——误报类别稳定(OS API 桥接 11、回调 endState、包装器词段、跨函数窗口),keyword 白名单方向的观测前提已达标待决策;稳定指标 = 黄金函数恢复率 97–100%
|
|
10
|
+
|
|
11
|
+
- **真实 C 语料验证 v1**(`blind-benchmark/scan-real-c.ts` + `REALWORLD_C_V1.md`):生产管线扫 libssh/redis/nginx——表面过滤后 16 flags 逐条人工标注全 FP(真实误报率观测:标记精确率 0%);「命名鸿沟」发现(exact-name 0 次触发,合成金标 95.7% F1 全靠按名+注解命中);误报源分类:回调生命周期 endState 12 / OS API 关键词桥接 3 / 跨函数窗口 1
|
|
12
|
+
- **提取器非生产表面过滤**(对齐 `tools/extract_ir.py` Python 先例):`collectCFiles` 跳过 `tests/test/examples/docs/docs_src/scripts/deps/vendor/third_party` 目录与 `test_*.c`/`*_test.c`——libssh 63→1、redis 1→0 条 flags;C 金标恢复率零漂移(97/97/89/98/100/99)
|
|
13
|
+
- **注解驱动真实项目演示**(`demo-real-c-redis/` + `REALWORLD_C_V2.md`):真实 redis acl.c 代码 + 3 条注解 → 合法流 APPROVED 85 零误报、植入 missing-auth-check 精确定位;标注成本 ~3 注解/协议——**注解驱动是 C 生产化的现实形态,可行性已验证**
|
|
14
|
+
- **引擎修复①(CamelCase 注解规则不可触达)**:注解合并同步注册 normalized 形态(加性,snake_case 注解零变化)
|
|
15
|
+
- **引擎修复②(注解合并晚于序列构建)**:P4.5 合并移到 `extractCallSequencesFromProject` 之前——有函数体的注解原语不再被内联掉、post 状态生效;与盲测 harness 语义对齐
|
|
16
|
+
- **引擎修复③(fixPath 输出真实函数名)**:`StateAnnotation.displayName` 机制——注解合并记录真实函数名,BFS 展开项目原语优先(stable sort 零漂移)+ 渲染映射;修复建议从通用规则名(`verify_token`)变为项目真实函数(`checkPasswordBasedAuth`),sdk 修复解析直接插入真实调用。三处边界如实记录:establish 赋值状态机不可见(L4 不投入)、模块认证 hook 在状态机外、单条 medium 违规不翻转 APPROVED(决策阈值层独立议题)
|
|
17
|
+
- **零漂移验证**:Python 协议盲测 v1.2 复跑 64 违规(报告仅时间戳差异);引擎相关套件 113/113 + 2 个新回归测试(tests/trust/engine.test.ts)
|
|
18
|
+
- **如实记录**:C 语言状态标签维持「研究」;不修项(按评估决策)——establish 赋值不可见(L4 不投入)、medium 违规不翻转 APPROVED(累计扣分设计逻辑,非缺陷)
|
|
19
|
+
|
|
20
|
+
## [3.7.4] — 2026-08-26
|
|
21
|
+
|
|
22
|
+
### 新增:C 语言 IR 提取(注册表第三语言)
|
|
23
|
+
|
|
24
|
+
- **`src/extract-ir-c.ts`**:纯 TS C 提取器(无子进程桥、无原生依赖)——函数签名(含多行、`static`/`inline`/`__attribute__`、指针/数组/函数指针参数)、调用列表(成员调用取 `->`/`.` 后的调用名,`goto` 合成 `goto_<label>`)、`@progmune`/`@protocol` 注解与 `@purpose/@tags/@requires/@produces/@useWhen/@inputs/@outputs` 文档标签(C 注释块镜像 Python 装饰器语法);注释/字符串感知的括号计数(修复 v2 提取器已知缺口,未改动 `sequence-extractor`——C 金标基准管线保持不动)
|
|
25
|
+
- **`LANGUAGE_EXTRACTORS` 注册 `c`**(detect `.c`/`.h`,extract `extractIRC`)——agent 循环、execute() 的 ir.json 写入与 MCP 自动生效;C 项目从纯正则回退切换到 IR-first 序列验证 + SSG 状态机,C 函数名进入词段匹配门控(仅项目函数);协议行与 protocols.json 规则名(`verify_password` 等)按名命中
|
|
26
|
+
- **端到端验证**:临时 C 项目上 extractProjectIR → evaluateTrust 走通——4 条植入违规全部精确定位(内置 auth×2 / db×1 + 自定义 pay 命名空间注解×1),合法链零误报(NEEDS_REVIEW 72 分);**应用级 C 金标 v1**(`blind-benchmark/scan-protocol-c-app.ts`,镜像 Python 盲测方法学):10 clean × 7 违规 → **P=87.5% / R=100% / F1=93.3%**(唯一 FP 为跨函数窗口边界,与 Python 盲测 T2×S5 同类)
|
|
27
|
+
- **规模化提取**(`blind-benchmark/scan-protocol-c.ts`):6 个 vendored 仓库(curl 5068 / libssh 3989 / nginx 3199 / openssl 15896 / nghttp2 1315 / redis 10170 函数,3.7.4 发布时代码状态——后续版本口径见 3.7.5)秒级提取,黄金函数恢复率 89–100%;旧 TLS 级金标上 SSG 命中 0/38(口径差异:SSG 无 TLS 规则,如实记录);nginx 3 FP 为 `ngx_*` 前缀包装器撞词段匹配(引擎层问题,记录待议,未动 SSG 桥避免 TS/Python 漂移)
|
|
28
|
+
- **修复:签名正则指数级回溯**——v2 风格类型 token 循环对 `name = ssh_userauth_kbdint_getname(...)` 类行穷举标识符切分(44 字符缓冲 ~11s),改为候选迭代(跳过关键字/类型名候选,返回类型从缓冲区前缀推导);libssh 提取 >15min(病态)→ 1.8s,回归测试已加
|
|
29
|
+
- **评审修复轮(detect/extract 口径、死代码、TU 绑定等)**:①`hasSourceFiles` SKIP_DIRS 补 `benchmarks`(与 extract 口径一致,本仓库自身不再误标 C);②`#if 0` 死代码块预处理剥离(真实仓库不平衡花括号不再腐蚀函数体计数);③顶层 `#` 行只跳自身不再吞相邻函数(openssl 14,394→15,896 函数,黄金函数恢复率升至 89–100%);④`buildCallSequences` 同文件定义优先绑定(跨文件同名 static 不再 last-wins 错绑,入口判定文件化;Python 盲测 v1.2 复测零漂移——报告与基线逐字节一致仅时间戳不同);⑤提取器取消调用去重(状态机重复调用有语义,双 close/重复 logout 可检出,与 TS/Python 提取器一致);⑥删死代码 `isCProject`;⑦混合 TS+C 项目回归测试。应用级 C 金标扩至 v2(11 clean × 11 违规 + helper 中介风格 + 逐命名空间分解):**TP 11/11 FP 1 FN 0 → P=91.7% / R=100% / F1=95.7%**(唯一 FP 为 do_logout 跨函数窗口边界)。已知系统性风险文档化:C 前缀包装器(`ngx_*` 等)撞词段匹配(nginx 3 FP),缓解方案(前缀剥离/连续词段)留待下一轮并强制盲测复跑
|
|
30
|
+
- **限制如实记录**:函数指针分发静态不可见(L3 结论不变)、宏/K&R/C++ 不解析、无数据流/指针分析(L4 无计划);提取器遍历跳过 vendored `benchmarks/`;`docs/c-language-status.md` 已更新(新路线小节 + 基准结果 + Decision record)
|
|
31
|
+
|
|
3
32
|
## [3.7.3] — 2026-08-24
|
|
4
33
|
|
|
34
|
+
### 中央免疫 Hub 上线 + 失败语料统一
|
|
35
|
+
|
|
36
|
+
- **中央 Hub 重新部署**(`progmune-runtime` 应用,`server/hub.js`):fly.toml 补 443 TLS 端口(此前仅 80,https 不可达),`https://progmune-runtime.fly.dev/report` 生效;数据落持久卷 `progmune_data`(`/app/immune_hub_data`),Dashboard `GET /api/dashboard` 可用(含 5 月历史 12 条指纹 + 冒烟测试 1 条)
|
|
37
|
+
- **上报链路打通**:`PROGMUNE_HUB` 指向中央 hub;`immune-reporter` 实测可连(游标增量上报,无新指纹时正常返回);端到端 POST 冒烟通过(received:1 / total:1)
|
|
38
|
+
- **统一失败语料写入路径**:`failure-collector.ts` 的 `CORPUS_DIR` 由仓库根 `failure-corpus/` 改为项目级 `.progmune_corpus/emitter-failures/`(与 `failure-corpus.ts` 同规则:`PROGMUNE_CORPUS_DIR || <PROGMUNE_PROJECT_DIR|cwd>/.progmune_corpus`),消除两套语料并存;dist 已重建
|
|
39
|
+
- **部署配置瘦身**:根 Dockerfile 改为零依赖(hub 仅需 `server/` + `public/`,去掉 npm install 与 dist 拷贝);`.dockerignore` 补 benchmarks/、.progmune_corpus/、dist/ 等大目录(构建上下文 1.1G → 数百 KB)
|
|
40
|
+
- **Hub 接口扩展**:`/api/dashboard` 新增 `topPatternsWeek`(本周高频错误模式 Top10)+ 全接口 CORS(`Access-Control-Allow-Origin: *`),供落地页跨域实时拉取
|
|
41
|
+
|
|
42
|
+
### 落地页新增「失败语料飞轮」板块(05)
|
|
43
|
+
|
|
44
|
+
- 三卡飞轮叙事(失败入库 → 中央汇聚 → 反馈增强)+ **实时本周高频错误模式 Top 10**(浏览器端 fetch `progmune-runtime.fly.dev/api/dashboard`,SVL 分级徽章 + 调用序列 + 次数;空态/不可达有兜底文案)
|
|
45
|
+
- 中英文切换覆盖(113 i18n 键);序号顺延:使用→06、路线图→07、社区→08;导航新增「飞轮」入口
|
|
46
|
+
|
|
5
47
|
### 文档全量一致性审计
|
|
6
48
|
|
|
7
49
|
- 全仓文档与当前进度核对并修正:
|
|
@@ -15,6 +57,9 @@
|
|
|
15
57
|
- **RUNTIME_ARCHITECTURE.md**:7 protocol definitions→27 命名空间/148 规则;业务指标表过时数字(FPR 97%、F1 27-41%)改为现行基准(0 FP、TS 98.5%/100%、C F1 16.5%)
|
|
16
58
|
- **项目全解.html**:hero 版本 v3.2.0→v3.7.2;21→27 命名空间(2 处);85.2% F1 旧数字→现行基准;5.7 基准表补协议盲测 v1.2 行;6.1 产品形态补社区双渠道机器人
|
|
17
59
|
- **投资人白皮书_v3.2.html**:Python"未激活"→✅;144→148 规则(2 处);PrintLab 案例对齐最终态(46→0 违规、44→87 APPROVED);P2 待办 Python/Go→Go/Java;里程碑表补 3.7 行;"当前能力(v3.2)"标注最新 npm 3.7.2
|
|
60
|
+
- **新增产品落地页 `index.html`**:产品介绍(为什么 / 是什么 / 核心能力 / 覆盖矩阵 / 使用方法 / 社区反馈)+ 双群二维码 + 自动回复说明 + 联系邮箱 shenlian1983@qq.com + 官网 tuxingren.xyz;自包含单文件(内联 CSS,无外部依赖),图片相对路径,可直接部署至 tuxingren.xyz
|
|
61
|
+
- **落地页上线 Fly.io**:新增 `web/` 静态站部署目录(nginx:alpine + fly.toml,app `progmune-web`,sin 区域,2 台机器);`fly certs add tuxingren.xyz` 已绑定域名证书,DNS 待用户按记录配置(A `149.248.206.6` / AAAA `2a09:8280:1::17a:e05f:0`);踩坑记录:Fly 远程构建不解析符号链接(web/ 内用真实文件副本)、hkg 区域已弃用(改用 sin)、文件权限需 644(nginx 用户可读)
|
|
62
|
+
- **落地页迭代**:新增中英文切换(data-i18n + localStorage);新增「下一步方向」板块(6 个轻量方向卡:更多语言 / Trust API SaaS / 协议扩展 / CI/CD 插件 / 行业基线 / 公开基准,依据 `docs/development-plan.md`);移除 tuxingren.xyz 全部引用(官网行 + 页脚链接 + i18n 键,待换新域名);去除联系卡片"自动回复"行
|
|
18
63
|
|
|
19
64
|
### 修复
|
|
20
65
|
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shenlian19831109
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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** | ⚠️ Research-only |
|
|
106
|
+
| **C** | ⚠️ Research-only | 3.7.4: IR extraction merged via the language registry — app-level protocol lifecycles (auth/db/file/payment) verifiable through the SSG state machine (app-level gold v2: **P=91.7% / R=100% / F1=95.7%**). TLS-level coverage still absent (old regex-route F1=16.5% is the historical baseline); L3/L4 conclusions unchanged. 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 (
|
|
155
|
+
### C (IR extraction + app-level protocol verification — research status)
|
|
156
156
|
|
|
157
|
-
C
|
|
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 validated on real redis ACL code (demo-real-c-redis). 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
|
|
|
@@ -212,7 +212,7 @@ Your feedback shapes Progmune. Scan the QR code to join the user discussion grou
|
|
|
212
212
|
|
|
213
213
|
## Scientific Foundation
|
|
214
214
|
|
|
215
|
-
Progmune is built on the premise that **LLM outputs are statistical performances, not reasoning** — a view developed by Subbarao Kambhampati et al. in the position paper ["Stop Anthropomorphizing Intermediate Tokens as Reasoning/Thinking Traces!"](https://arxiv.org/abs/
|
|
215
|
+
Progmune is built on the premise that **LLM outputs are statistical performances, not reasoning** — a view developed by Subbarao Kambhampati et al. in the position paper ["Stop Anthropomorphizing Intermediate Tokens as Reasoning/Thinking Traces!"](https://arxiv.org/abs/2504.09762) (arXiv:2504.09762, 2025) and elaborated in his ICML 2026 talk "On the Role of Verifiers and Thinking Traces in Reasoning Models". Rather than trusting what the model says about code, Progmune verifies what the program actually does — using protocol state machines, IR extraction, and evidence-backed decision chains.
|
|
216
216
|
|
|
217
217
|
→ [Investor Whitepaper](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/Progmune_投资人白皮书_v2.0.html) · [Trust Decision Model](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/ai-trust-decision-model-v1.md)
|
|
218
218
|
|
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** | ⚠️ 仅研究 | 3.7.4:IR 提取接入多语言注册表——应用级协议生命周期(认证/数据库/文件/支付)经 SSG 状态机可验证(应用级金标 v2:**P=91.7% / R=100% / F1=95.7%**)。TLS 级覆盖仍无(旧正则口径 F1=16.5% 为历史基线);L3/L4 结论不变。见 [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
|
|
155
|
+
### C(IR 提取 + 应用级协议验证——研究状态)
|
|
156
156
|
|
|
157
|
-
C
|
|
157
|
+
3.7.4 起 C 拥有多语言注册表中的 IR 提取器:C 项目进入 IR-first 序列验证 + SSG 状态机。应用级协议生命周期(认证/数据库/文件/支付)在 C 上可验证——应用级金标 v2:**11/11 违规全检出(召回 100%)、1 误报、F1=95.7%**;注解驱动验证在真实 redis ACL 代码上走通(demo-real-c-redis)。提取覆盖大型真实仓库(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
|
|
|
@@ -211,7 +211,7 @@ SDK (src/sdk.ts) verify() → APPROVED / NEEDS_REVIEW / BLOCKED
|
|
|
211
211
|
|
|
212
212
|
## 科学基础
|
|
213
213
|
|
|
214
|
-
Progmune 建立在"**LLM 输出是统计表演而非推理**"这一前提上——该观点源自 Subbarao Kambhampati 等人的立场论文 ["Stop Anthropomorphizing Intermediate Tokens as Reasoning/Thinking Traces!"](https://arxiv.org/abs/
|
|
214
|
+
Progmune 建立在"**LLM 输出是统计表演而非推理**"这一前提上——该观点源自 Subbarao Kambhampati 等人的立场论文 ["Stop Anthropomorphizing Intermediate Tokens as Reasoning/Thinking Traces!"](https://arxiv.org/abs/2504.09762)(arXiv:2504.09762,2025),并在其 ICML 2026 演讲 "On the Role of Verifiers and Thinking Traces in Reasoning Models" 中展开。Progmune 不信任模型对代码的说法,而是用协议状态机、IR 提取与证据链验证程序实际行为。
|
|
215
215
|
|
|
216
216
|
→ [投资人白皮书](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/Progmune_投资人白皮书_v2.0.html) · [信任决策模型](https://github.com/shenlian19831109/progmune-runtime/blob/main/docs/ai-trust-decision-model-v1.md)
|
|
217
217
|
|
package/dist/call-sequence.js
CHANGED
|
@@ -13,10 +13,17 @@
|
|
|
13
13
|
* 不做数据流/指针/分支分析;跨文件依赖 IR 的 calls[] 图。
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.MAX_SEQUENCE_CALLS = void 0;
|
|
16
17
|
exports.isProjectFn = isProjectFn;
|
|
17
18
|
exports.collectProjectFunctionNames = collectProjectFunctionNames;
|
|
18
19
|
exports.buildCallSequences = buildCallSequences;
|
|
19
20
|
const MAX_DEPTH = 4;
|
|
21
|
+
/**
|
|
22
|
+
* 每序列调用预算上限。真实语料实测:Python 盲测语料 350 序列最大 23、
|
|
23
|
+
* TS 自身 IR 469 序列最大 824——2,000 对现有语料零影响;C 巨型函数
|
|
24
|
+
* (openssl 单序列可达 1M+ 调用)在预算处截断。
|
|
25
|
+
*/
|
|
26
|
+
exports.MAX_SEQUENCE_CALLS = 2000;
|
|
20
27
|
/** 项目函数判定:有真实文件且非外部导入条目(external 条目无函数体可内联)。 */
|
|
21
28
|
function isProjectFn(f) {
|
|
22
29
|
return !f.external && !!f.file && f.file !== "(external)";
|
|
@@ -53,44 +60,79 @@ function collectProjectFunctionNames(ir) {
|
|
|
53
60
|
* (其调用名保留给规则匹配),不内联其函数体——否则 create_session 等
|
|
54
61
|
* 规则函数的平凡函数体会把调用名"吞掉"
|
|
55
62
|
*/
|
|
56
|
-
function buildCallSequences(ir, keepNames) {
|
|
63
|
+
function buildCallSequences(ir, keepNames, maxCalls = exports.MAX_SEQUENCE_CALLS) {
|
|
64
|
+
// 全局按名回退 + 同文件优先映射:C 中跨文件同名 static 函数极常见
|
|
65
|
+
// (每个 .c 都有 static cleanup/helper),名字级 Map 会让 last-wins 的
|
|
66
|
+
// 定义绑定到错误的调用方。同翻译单元(文件)定义优先解析——
|
|
67
|
+
// 文件内回调(cf->close_one() → 本文件 static close_one)因此接回序列构建;
|
|
68
|
+
// 跨文件函数指针分发仍不可见(L3 结论不变)。
|
|
57
69
|
const fnMap = new Map();
|
|
70
|
+
const fnMapByFile = new Map();
|
|
58
71
|
for (const f of ir) {
|
|
59
|
-
if (isProjectFn(f))
|
|
60
|
-
|
|
72
|
+
if (!isProjectFn(f))
|
|
73
|
+
continue;
|
|
74
|
+
fnMap.set(f.name, f);
|
|
75
|
+
let byFile = fnMapByFile.get(f.file);
|
|
76
|
+
if (!byFile) {
|
|
77
|
+
byFile = new Map();
|
|
78
|
+
fnMapByFile.set(f.file, byFile);
|
|
79
|
+
}
|
|
80
|
+
if (!byFile.has(f.name))
|
|
81
|
+
byFile.set(f.name, f);
|
|
61
82
|
}
|
|
62
|
-
|
|
83
|
+
/** 调用解析:同文件定义优先,全局按名回退(跨文件同名时每个文件绑自己的) */
|
|
84
|
+
const resolveCall = (fromFile, name) => fnMapByFile.get(fromFile)?.get(name) ?? fnMap.get(name);
|
|
85
|
+
// 被项目函数调用过的函数不是入口(其片段并入调用方展开序列)——
|
|
86
|
+
// 按 文件+名字 粒度判定,避免 A 文件的 static x 被 B 文件的调用误判非入口
|
|
87
|
+
const fnKey = (file, name) => file + "::" + name;
|
|
63
88
|
const calledBy = new Set();
|
|
64
89
|
for (const f of ir) {
|
|
65
90
|
if (!isProjectFn(f))
|
|
66
91
|
continue;
|
|
67
92
|
for (const c of f.calls || []) {
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
const resolved = resolveCall(f.file, c);
|
|
94
|
+
if (resolved)
|
|
95
|
+
calledBy.add(fnKey(resolved.file, resolved.name));
|
|
70
96
|
}
|
|
71
97
|
}
|
|
98
|
+
/** 调用预算:预算制展开——入口自身调用按序优先,预算耗尽即停(截断),
|
|
99
|
+
* 不在事后截断百万级序列(内存/时间双浪费) */
|
|
100
|
+
const budget = { left: maxCalls, truncated: false };
|
|
72
101
|
/** 展开函数体内的调用(入口序列 = 函数体调用,不含函数自己的名字) */
|
|
73
102
|
const expandBody = (fn, depth, visiting) => {
|
|
103
|
+
if (budget.left <= 0) {
|
|
104
|
+
budget.truncated = true;
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
74
107
|
const out = [];
|
|
75
108
|
for (const c of fn.calls || []) {
|
|
76
109
|
if (typeof c !== "string" || c.startsWith("__progmune_"))
|
|
77
110
|
continue;
|
|
78
|
-
|
|
111
|
+
if (budget.left <= 0) {
|
|
112
|
+
budget.truncated = true;
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
out.push(...expandCall(c, depth, visiting, fn.file));
|
|
79
116
|
}
|
|
80
117
|
return out;
|
|
81
118
|
};
|
|
82
|
-
const expandCall = (name, depth, visiting) => {
|
|
119
|
+
const expandCall = (name, depth, visiting, fromFile) => {
|
|
83
120
|
if (depth > MAX_DEPTH || visiting.has(name))
|
|
84
121
|
return [];
|
|
85
|
-
const fn =
|
|
86
|
-
//
|
|
87
|
-
if (!fn || (keepNames && keepNames.has(name)))
|
|
122
|
+
const fn = resolveCall(fromFile, name);
|
|
123
|
+
// 外部调用或规则函数:调用名保留给匹配层,不内联(记 1 个调用)
|
|
124
|
+
if (!fn || (keepNames && keepNames.has(name))) {
|
|
125
|
+
budget.left--;
|
|
88
126
|
return [name];
|
|
127
|
+
}
|
|
89
128
|
// 叶子函数(函数体只调外部原语)是协议原语或叶子 helper:
|
|
90
129
|
// 保留名字,不内联——否则 S5 改名协议函数的平凡函数体会吞掉调用名
|
|
91
|
-
const hasProjectCalls = (fn.calls || []).some((c) =>
|
|
92
|
-
if (!hasProjectCalls)
|
|
130
|
+
const hasProjectCalls = (fn.calls || []).some((c) => resolveCall(fn.file, c));
|
|
131
|
+
if (!hasProjectCalls) {
|
|
132
|
+
budget.left--;
|
|
93
133
|
return [name];
|
|
134
|
+
}
|
|
135
|
+
// 内联:函数体自身的调用在递归内各自记账
|
|
94
136
|
visiting.add(name);
|
|
95
137
|
const out = expandBody(fn, depth + 1, visiting);
|
|
96
138
|
visiting.delete(name);
|
|
@@ -100,14 +142,16 @@ function buildCallSequences(ir, keepNames) {
|
|
|
100
142
|
for (const f of ir) {
|
|
101
143
|
if (!isProjectFn(f))
|
|
102
144
|
continue;
|
|
103
|
-
if (calledBy.has(f.name))
|
|
145
|
+
if (calledBy.has(fnKey(f.file, f.name)))
|
|
104
146
|
continue; // 非入口:片段并入调用方
|
|
105
147
|
if (keepNames && keepNames.has(f.name))
|
|
106
148
|
continue; // 协议原语不是入口:只在调用链内验证
|
|
149
|
+
budget.left = maxCalls;
|
|
150
|
+
budget.truncated = false;
|
|
107
151
|
const calls = expandBody(f, 0, new Set());
|
|
108
152
|
if (calls.length === 0)
|
|
109
153
|
continue;
|
|
110
|
-
sequences.push({ calls, file: f.file, function: f.name });
|
|
154
|
+
sequences.push({ calls, file: f.file, function: f.name, truncated: budget.truncated || undefined });
|
|
111
155
|
}
|
|
112
156
|
return sequences;
|
|
113
157
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* call-sequence (P4.6 跨函数传播) — 单元测试。
|
|
4
|
+
*
|
|
5
|
+
* 重点覆盖 2026-08-27 引入的同翻译单元(文件)绑定语义:
|
|
6
|
+
* 调用解析优先同文件定义(C 跨文件同名 static 函数不再 last-wins 错绑),
|
|
7
|
+
* 入口判定按 文件+名字 粒度。
|
|
8
|
+
*
|
|
9
|
+
* 注意叶子规则:只调外部原语的函数保留名字不内联(S5 改名协议函数的
|
|
10
|
+
* 保护),夹具需用两级项目函数链使绑定可判别。
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const vitest_1 = require("vitest");
|
|
14
|
+
const call_sequence_1 = require("./call-sequence");
|
|
15
|
+
/** 构造最小 FunctionInfo(isProjectFn 契约:external=false + 真实 file) */
|
|
16
|
+
function fn(name, file, calls) {
|
|
17
|
+
return { name, params: [], returnType: "void", file, calls, exported: true, external: false };
|
|
18
|
+
}
|
|
19
|
+
(0, vitest_1.describe)("buildCallSequences(文件级绑定)", () => {
|
|
20
|
+
(0, vitest_1.it)("跨文件同名 static 函数:调用绑定到同文件定义,而不是 last-wins", () => {
|
|
21
|
+
const ir = [
|
|
22
|
+
// A 文件:static x → distinct_a(本文件项目函数)
|
|
23
|
+
fn("x", "a.c", ["distinct_a"]),
|
|
24
|
+
fn("distinct_a", "a.c", ["g"]),
|
|
25
|
+
fn("entry_a", "a.c", ["x"]),
|
|
26
|
+
// B 文件:static x → distinct_b(若 last-wins 且 B 后注册,entry_a 会错绑到 B 的 x)
|
|
27
|
+
fn("x", "b.c", ["distinct_b"]),
|
|
28
|
+
fn("distinct_b", "b.c", ["h"]),
|
|
29
|
+
fn("entry_b", "b.c", ["x"]),
|
|
30
|
+
];
|
|
31
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir);
|
|
32
|
+
const byFn = new Map(seqs.map((s) => [s.function, s.calls]));
|
|
33
|
+
(0, vitest_1.expect)(byFn.get("entry_a")).toEqual(["distinct_a"]);
|
|
34
|
+
(0, vitest_1.expect)(byFn.get("entry_b")).toEqual(["distinct_b"]);
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)("文件内回调接回序列:cf->close_one() 产出 close_one,同文件定义被内联", () => {
|
|
37
|
+
// 提取器对 cf->close_one() 只产出调用名 close_one;同文件定义 close_one 时
|
|
38
|
+
// 文件级绑定将其函数体(经 notify_peer)内联进调用方序列
|
|
39
|
+
const ir = [
|
|
40
|
+
fn("close_one", "conn.c", ["notify_peer"]),
|
|
41
|
+
fn("notify_peer", "conn.c", ["send_fin"]),
|
|
42
|
+
fn("shutdown_conn", "conn.c", ["close_one"]),
|
|
43
|
+
];
|
|
44
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir);
|
|
45
|
+
(0, vitest_1.expect)(seqs).toHaveLength(1);
|
|
46
|
+
(0, vitest_1.expect)(seqs[0].function).toBe("shutdown_conn");
|
|
47
|
+
(0, vitest_1.expect)(seqs[0].calls).toEqual(["notify_peer"]);
|
|
48
|
+
});
|
|
49
|
+
(0, vitest_1.it)("入口判定按文件粒度:A 的 x 被调用非入口,B 的同名 x 未被调用仍是入口", () => {
|
|
50
|
+
const ir = [
|
|
51
|
+
fn("x", "a.c", ["distinct_a"]),
|
|
52
|
+
fn("distinct_a", "a.c", ["g"]),
|
|
53
|
+
fn("entry_a", "a.c", ["x"]),
|
|
54
|
+
fn("x", "b.c", ["h"]), // B 的 x 无调用者 → 仍是入口
|
|
55
|
+
];
|
|
56
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir);
|
|
57
|
+
const names = seqs.map((s) => s.function).sort();
|
|
58
|
+
(0, vitest_1.expect)(names).toEqual(["entry_a", "x"]);
|
|
59
|
+
// 唯一名为 x 的序列来自 b.c(A 的 x 已并入 entry_a)
|
|
60
|
+
const xSeq = seqs.find((s) => s.function === "x");
|
|
61
|
+
(0, vitest_1.expect)(xSeq.file).toBe("b.c");
|
|
62
|
+
(0, vitest_1.expect)(xSeq.calls).toEqual(["h"]);
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("无同文件定义时回退全局按名(跨文件唯一名的既有行为不变)", () => {
|
|
65
|
+
const ir = [
|
|
66
|
+
fn("helper", "lib.c", ["util"]),
|
|
67
|
+
fn("util", "lib.c", ["primitive"]),
|
|
68
|
+
fn("entry", "app.c", ["helper"]),
|
|
69
|
+
];
|
|
70
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir);
|
|
71
|
+
(0, vitest_1.expect)(seqs).toHaveLength(1);
|
|
72
|
+
(0, vitest_1.expect)(seqs[0].calls).toEqual(["util"]);
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("规则名保留单元不内联(keepNames),文件级绑定不影响", () => {
|
|
75
|
+
const ir = [
|
|
76
|
+
fn("verify_password", "auth.c", ["check_hash"]),
|
|
77
|
+
fn("login", "auth.c", ["verify_password"]),
|
|
78
|
+
];
|
|
79
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir, new Set(["verify_password"]));
|
|
80
|
+
(0, vitest_1.expect)(seqs).toHaveLength(1);
|
|
81
|
+
(0, vitest_1.expect)(seqs[0].function).toBe("login");
|
|
82
|
+
(0, vitest_1.expect)(seqs[0].calls).toEqual(["verify_password"]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.describe)("buildCallSequences(调用预算 / 截断,兆级序列防护)", () => {
|
|
86
|
+
(0, vitest_1.it)("超预算截断:序列长度 = maxCalls,truncated 标记", () => {
|
|
87
|
+
const ext = Array.from({ length: 15 }, (_, i) => `ext_${i}`);
|
|
88
|
+
const ir = [fn("entry", "a.c", ext)];
|
|
89
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir, undefined, 10);
|
|
90
|
+
(0, vitest_1.expect)(seqs).toHaveLength(1);
|
|
91
|
+
(0, vitest_1.expect)(seqs[0].calls).toHaveLength(10);
|
|
92
|
+
(0, vitest_1.expect)(seqs[0].calls).toEqual(ext.slice(0, 10)); // 入口自身调用按序优先
|
|
93
|
+
(0, vitest_1.expect)(seqs[0].truncated).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.it)("预算内序列:无 truncated 标记(现有语料零影响)", () => {
|
|
96
|
+
const ir = [fn("entry", "a.c", ["g", "h"])];
|
|
97
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir, undefined, 2000);
|
|
98
|
+
(0, vitest_1.expect)(seqs).toHaveLength(1);
|
|
99
|
+
(0, vitest_1.expect)(seqs[0].calls).toEqual(["g", "h"]);
|
|
100
|
+
(0, vitest_1.expect)(seqs[0].truncated).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
(0, vitest_1.it)("内联超预算:已发出的调用保留,入口后续调用被截(召回边界语义)", () => {
|
|
103
|
+
const bigBody = Array.from({ length: 20 }, (_, i) => `big_call_${i}`);
|
|
104
|
+
const ir = [
|
|
105
|
+
fn("helper", "a.c", ["ext"]), // 项目函数:big 因调它而可内联(叶子规则)
|
|
106
|
+
fn("big", "a.c", ["helper", ...bigBody]),
|
|
107
|
+
fn("entry", "a.c", ["big", "tail_call"]),
|
|
108
|
+
];
|
|
109
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir, undefined, 5);
|
|
110
|
+
(0, vitest_1.expect)(seqs).toHaveLength(1);
|
|
111
|
+
(0, vitest_1.expect)(seqs[0].calls).toEqual(["helper", "big_call_0", "big_call_1", "big_call_2", "big_call_3"]);
|
|
112
|
+
(0, vitest_1.expect)(seqs[0].truncated).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
(0, vitest_1.it)("多入口各自独立预算", () => {
|
|
115
|
+
const ext = Array.from({ length: 8 }, (_, i) => `ext_${i}`);
|
|
116
|
+
const ir = [fn("e1", "a.c", ext), fn("e2", "b.c", ext)];
|
|
117
|
+
const seqs = (0, call_sequence_1.buildCallSequences)(ir, undefined, 5);
|
|
118
|
+
(0, vitest_1.expect)(seqs).toHaveLength(2);
|
|
119
|
+
for (const s of seqs) {
|
|
120
|
+
(0, vitest_1.expect)(s.calls).toHaveLength(5);
|
|
121
|
+
(0, vitest_1.expect)(s.truncated).toBe(true);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.describe)("collectProjectFunctionNames", () => {
|
|
126
|
+
(0, vitest_1.it)("全名/裸名/小写三形态收录", () => {
|
|
127
|
+
const ir = [fn("FlowService.svc_x", "a.ts", [])];
|
|
128
|
+
const names = (0, call_sequence_1.collectProjectFunctionNames)(ir);
|
|
129
|
+
(0, vitest_1.expect)(names.has("FlowService.svc_x")).toBe(true);
|
|
130
|
+
(0, vitest_1.expect)(names.has("svc_x")).toBe(true);
|
|
131
|
+
(0, vitest_1.expect)(names.has("flowservice.svc_x")).toBe(true);
|
|
132
|
+
(0, vitest_1.expect)(names.has("svc_x".toLowerCase())).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
(0, vitest_1.it)("external 条目不收录", () => {
|
|
135
|
+
const external = { name: "readFileSync", params: [], returnType: "void", file: "(external)", calls: [], external: true };
|
|
136
|
+
(0, vitest_1.expect)((0, call_sequence_1.isProjectFn)(external)).toBe(false);
|
|
137
|
+
(0, vitest_1.expect)((0, call_sequence_1.collectProjectFunctionNames)([external]).size).toBe(0);
|
|
138
|
+
});
|
|
139
|
+
});
|