verifact 0.0.0-stage → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 FIBEMATE
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
package/README.md CHANGED
@@ -1,3 +1,95 @@
1
- # Temporary Holding Version
1
+ # verifact — 事实哨兵
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **把文档里的硬数字声明绑定到真实产出物上逐条核验,漂移即红。**
4
+
5
+ Markdown 文档里的性能基准、测试计数、版本号——这些数字会漂移。代码改了三轮,README 里的数字还是三个月前的。verifact 自动化「文档声明 vs 产出物」的交叉比对,把核对从肉眼 grep 变成门禁。
6
+
7
+ Zero external dependencies. Pure Node.js.
8
+
9
+ ## 核心流程
10
+
11
+ ```
12
+ [文档] ──→ 抽取带单位的关键数字 ──→ 按键名+数值绑定到产出物 ──→ 六态判定
13
+ ↑
14
+ [基准/日志/TSR 等产出物]
15
+ ```
16
+
17
+ ### 六态判定
18
+
19
+ | 判定 | 含义 | 处置 |
20
+ |:--|:--|:--|
21
+ | `verified` | 文档值与产出物在容差内一致 | 绿 |
22
+ | `drift` | 绑定成功但数值超出容差 | 🔴 门禁拦截 |
23
+ | `ambiguous` | 多个产出物候选冲突 | 交人工 |
24
+ | `unbound` | 找不到产出物依据 | 交人工 |
25
+ | `ref-matched` | 声明引用 `lg-XXX` 等实体,在扫描范围内找到 | 绿 |
26
+ | `ref-missing` | 引用实体不在扫描范围内 | 交人工 |
27
+
28
+ ## 用法
29
+
30
+ ```
31
+ npm install # 零依赖,仅安装
32
+ ```
33
+
34
+ ### 核验文档
35
+
36
+ ```bash
37
+ node bin/verifact.js verify # 默认配置,drift 即退出码 1
38
+ node bin/verifact.js verify --fail-on none # 仅报告,不拦截
39
+ node bin/verifact.js verify --format md --out report.md
40
+ ```
41
+
42
+ ### 审查单条声明
43
+
44
+ ```bash
45
+ node bin/verifact.js explain <claimId>
46
+ ```
47
+
48
+ ### 历史对比
49
+
50
+ ```bash
51
+ node bin/verifact.js diff --since 3 # 最近 3 次运行的状态迁移
52
+ ```
53
+
54
+ ### 本地看板
55
+
56
+ ```bash
57
+ node bin/verifact.js serve --port 8080
58
+ ```
59
+
60
+ ## 退出码
61
+
62
+ | 码 | 含义 |
63
+ |---|-------|
64
+ | 0 | 核验通过 / 无命中门禁条件 |
65
+ | 1 | 命中门禁条件(存在 drift 等) |
66
+ | 2 | 用法或配置错误 |
67
+
68
+ ## 配置
69
+
70
+ 项目根目录放置 `verifact.json`:
71
+
72
+ - `docs.roots` / `artifacts.roots` — 要扫描的目录或文件;**相对路径以配置文件所在目录为基准**(不是 cwd)
73
+ - `artifacts` — 产出物目录列表
74
+ - `ignore` — 已知可忽略的声明(不计入统计)
75
+ - `deny` — 人工拒绑:声明仍留在报告与总数里,但不做自动绑定,note 写出理由
76
+
77
+ 详见 [`verifact.json.example`](./verifact.json.example)。
78
+
79
+ ## 设计
80
+
81
+ 详见 [`VERIFACT-DESIGN.md`](./VERIFACT-DESIGN.md):六态判定设计、绑定算法、ignore/deny 策略。
82
+
83
+ ## 项目状态
84
+
85
+ | 项 | 状态 |
86
+ |---|-------|
87
+ | 回归测试 | ✅ 15 项 (`node test/run.js`) |
88
+ | CI 集成 | ✅ sentinel 门禁(`fail-on-drift`) |
89
+ | 平台 | Node.js ≥18,零外部依赖 |
90
+ | 发布 | 私有包(`@fibemate/verifact`),未发布 npm |
91
+ | 许可证 | Apache-2.0 |
92
+
93
+ ## LICENSE
94
+
95
+ Apache-2.0 — see [LICENSE](../LICENSE).
@@ -0,0 +1,337 @@
1
+ # verifact 设计文档
2
+
3
+ > 事实哨兵:把文档里的硬数字绑定到产出物上,漂移即红。
4
+ > 版本:0.1.0(2026-09-29)
5
+ > 位置:fibemate-tools/verifact/
6
+
7
+ ---
8
+
9
+ ## 一、问题:文档里的数字会漂移
10
+
11
+ 任何有硬数字的项目都会遇到同一类问题:
12
+
13
+ - 性能基准写在 README 里,代码在三个月后重测,数字开始各走各的
14
+ - 测试计数在 CI 里是 36,文档里是 61(合并重构后未更新)
15
+ - 一个「提升 15-31x」的声明,底数来自一个已经不存在的版本
16
+ - 头条数字 `p95=78.5ms` 挂在最显眼的位置,但产出物里找不到来源
17
+
18
+ **这些不是「文档说谎」——是「文档写于某时刻,产出物在另一时刻」。**
19
+
20
+ 人工核对靠 grep 和肉眼。**fibemate 主仓 341 份文档、2251 条声明**——每轮发稿前肉眼扫一遍不现实。
21
+
22
+ verifact 占的位置:**把「文档声明 vs 产出物」的比对从人工变成自动化门禁。**
23
+
24
+ ---
25
+
26
+ ## 二、核心设计:六态判定
27
+
28
+ 三阶段流程:
29
+
30
+ ### 阶段 1:抽取
31
+
32
+ 扫描 markdown(默认只扫代码块外的正文),把带单位的数字连同键名抽出来。
33
+
34
+ - 输入样例:`103µs`、`26,115 states`、`654 行`
35
+ - 每条声明记录:`{file, line, key, value}`
36
+
37
+ ### 阶段 2:绑定
38
+
39
+ 把声明和产出物对接。产出物来源在 `verifact.json` 里登记:
40
+
41
+ - benchmark 的 JSON / 日志
42
+ - 主仓 scripts
43
+ - 时间戳目录
44
+ - 核心库
45
+
46
+ 绑定按**键名相似度**和**数值容差**打分(默认阈值 `minScore: 0.45`,相对容差 0.5%)。
47
+
48
+ ### 阶段 3:判定
49
+
50
+ 六种结果,**只有一种是红的**:
51
+
52
+ | 判定 | 含义 | 处置 |
53
+ |:--|:--|:--|
54
+ | `verified` | 文档值和产出物在容差内一致 | 绿 |
55
+ | **`drift`** | 绑定成功但数值超出容差 | **红,门禁拦截** |
56
+ | `ambiguous` | 多个产出物候选冲突,或跨模块同名键 | 交人工 |
57
+ | `unbound` | 找不到产出物依据 | 交人工 |
58
+ | `引用存在` | 声明引用 `lg-XXX` 等实体,在扫描范围内找到 | 绿 |
59
+ | `引用缺失` | 同上,但找不到 | 交人工 |
60
+
61
+ ---
62
+
63
+ ## 三、设计决策:为什么不把 `unbound` 当错
64
+
65
+ **这是本工具最重要的一个立场。**
66
+
67
+ 如果把 `unbound` 也算失败——工具会在第一天就被噪音淹没。
68
+
69
+ `unbound` 1836 条不等于文档在说谎:
70
+ - 可能证据没导出成机器可读格式
71
+ - 可能是叙述性文字(「我们做了 X」)
72
+ - 可能是外部引用(论文、标准)
73
+
74
+ **红只留给一种情况:找到了依据,但数字对不上。**
75
+
76
+ 这个分层的关键是:**「没证据」≠「错」。** 把两者混为一谈的工具,会在用户第一次看报告时就失去信任。
77
+
78
+ 同理:`ambiguous` 也不红——多个候选冲突时,机器不知道该信谁,交人工。
79
+
80
+ ---
81
+
82
+ ## 四、设计决策:为什么不用 AST
83
+
84
+ **零外部依赖。** 扫描是正则 + 字符串匹配,不引入 acorn / esprima / 任何解析器。
85
+
86
+ **取舍逻辑**:
87
+ - 完整 AST 意味着「我理解了语义」的承诺——这个承诺给不起
88
+ - 掩码扫描(字符串匹配)是启发式,会漏掉混淆写法,也可能被字符串里的巧合命中
89
+ - **这些局限写在 README 的「已知局限」一节,不藏在脚注里**
90
+
91
+ **代价明码标价**:漏报的代价由「人是最后一道闸」兜底,误报的代价由「判决权在人手里」兜底。
92
+
93
+ **定位透明的好处**:每条 finding 的 `file` 和 `line` 都是真实的,人工复核直接跳到那行。
94
+
95
+ ---
96
+
97
+ ## 五、ignore 与 deny:把「口径差异」和「真过期」分开
98
+
99
+ **问题**:同一个数字,两份产出物各说各话——不是「文档错」,是「口径不同」。
100
+
101
+ 例子:
102
+ - 文档引的是 JS 基准(2026-07-18 实测)
103
+ - verifact 查到的是硬件基准(FPGA 最新数据)
104
+ - **两套数据都对,只是测的东西不同**
105
+
106
+ **解法**:配置里 `ignore` 和 `deny` 两段,人工复核后的沉淀。
107
+
108
+ ```json
109
+ {
110
+ "ignore": [
111
+ { "file": "**/PQC_MIGRATION_PLAN.md", "key": "kem-keygen", "kind": "measure",
112
+ "note": "JS 估算值 vs 硬件基准,口径不同。TODO: 修绑定指向 JS benchmark" }
113
+ ],
114
+ "deny": [
115
+ { "file": "**/TECHNICAL-VERIFICATION.md", "key": "rsa-2048",
116
+ "note": "RSA 对照列,不与 ML-KEM keygen 比对" }
117
+ ]
118
+ }
119
+ ```
120
+
121
+ **每条带 note,说明为什么。** ignore 不是掩盖——是**把「口径差异」和「真过期」分开登记**。
122
+
123
+ ### 处置分类(dispositions)—— 区分「没有证据」与「不适用证据」
124
+
125
+ `ignore` 解决的是「同一条声明、两种口径」,它回答不了另一个问题:**这个数字压根不是产物属性**。
126
+ 清单进度(`0/28`)、一次性测量(`7.8µs`)、实验参数(`n=40, q=1009`)都属于后者 ——
127
+ 硬把它们绑到静态产物上,只会逼作者写出假绑定。
128
+
129
+ 于是增加一层**处置分类**(RFC: `../docs/rfc-verifact-dispositions.md`,2026-10-26 合并):
130
+
131
+ | disposition | 必填参数 | 含义 | 进覆盖率分母 |
132
+ |:--|:--|:--|:--|
133
+ | `code-artifact` | 无 | 默认,今天的全部行为 | 是 |
134
+ | `measured-once` | `artifact=` | 某次运行的测量值 | 否 |
135
+ | `progress` | `owner=` | 清单/看板的进度统计 | 否 |
136
+ | `narrative` | `why=` | 实验参数、描述性数字 | 否 |
137
+
138
+ 写法是 HTML 注释(GitHub 渲染不可见),三种作用域:文件级(前 5 行)、区域级(`begin`/`end` 成对)、单条级(行尾或紧邻上一行)。
139
+
140
+ 三条硬规则,缺一即**报错**:
141
+
142
+ 1. 非 `code-artifact` 必须指名「谁拥有这个数字」,否则就是无主数字;
143
+ 2. **只有 `owner` 可以继承**(区域内单条 `progress` 可省略),`artifact` / `since` / `why` 必须就地写;
144
+ 3. 声明写错 = **整次运行失败**,不是记一条 `docScanError` 就放过 —— 后者会让整份文档的声明静默消失,报告少了一片数字却仍然绿。
145
+
146
+ 报告**永远并列打印两个分母**:`7.0% (code-artifact 285 条)` 与 `7.0% (全部受判 285 条)`。
147
+ 换了分母的「覆盖率上升」不算上升。
148
+
149
+ 两条**反洗白警示**(没有它们,整文件标注就是「把分母搬走」的合法外衣):
150
+
151
+ - **整份文件没有一条 code-artifact** → 单列出来:`⚠ <file>:整份文件 N 条声明全部非 code-artifact,该文件对覆盖率零贡献。`
152
+ - **全报告非 code-artifact 占比 > 50%** → 顶部警示,并写明「换分母不等于覆盖率上升」。
153
+
154
+ 警示不是错误:它不阻止运行,只保证「清空一个文件的分母」这件事在报告里**看得见**。
155
+
156
+ ---
157
+
158
+ ### 声明身份:stableKey(2026-10-26,issue #96)
159
+
160
+ `id` 是给人看的位置标签(`<file>:L<line>:<kind>:<key>`),**不是身份**。拿它当身份会坏两次:
161
+
162
+ 1. 在声明上方插入一行注释,整份文件的 `id` 全部改名 → `diff` 把排版改动读成大规模迁移;
163
+ 2. 反过来更坏:按 `id` 比对时,「谁从 verified 变成 unbound」会被一堆同名改名淹没,看起来像没变化。
164
+
165
+ 身份改用内容寻址:
166
+
167
+ ```
168
+ stableKey = <file>:<kind>:<key>:<sha256(file, kind, key, value.raw, context)[0..12]>
169
+ ```
170
+
171
+ `context` 是声明所在行,只做三件归一化:CRLF → LF、剥掉 HTML 注释、连续空白压成一个空格并去首尾。
172
+ 三条都对应一次真实的假迁移:换平台换行符、行尾加 `disposition` 注释、调整缩进。
173
+
174
+ 同一文件里出现两条**完全相同**的声明(同一行写了两次同一个数,或复制粘贴的残骸),第二条起追加 `#2`、`#3`,
175
+ 并在报告里单列「重复声明」。不合并它们:合并会让两条不同的声明共用一个历史位,那是静默丢数据。
176
+
177
+ ### sealed:哈希存证目录(2026-10-26)
178
+
179
+ `www/docs/tsa/**` 里的文件本身就是 TSA 时间戳的对象,插一行注释就改变哈希、破坏存证。
180
+ 处置分类(disposition)要改文件,这条路走不通;`ignore` 只能按 key/kind 逐条写,治不了「整个目录」。
181
+ 所以 sealed 是**配置层**的东西,不是作者可声明的 disposition:
182
+
183
+ ```json
184
+ "sealed": ["docs/tsa/**"]
185
+ ```
186
+
187
+ 命中的声明得到 `status: sealed`:不进判定、不进覆盖率分母、不算 unbound、不算 ignored,单独一行
188
+ `Sealed (by hash): N 条(M 个文件…)`。它们仍然出现在 JSON 报告的 `claims` 数组里(带 note 指明命中的规则)。
189
+
190
+ 两条防假安全:
191
+
192
+ 1. `sealed` 只接受**路径 glob 字符串**。写成 `{file: ...}` 这类规则对象直接报配置错误——否则规则静默不生效,
193
+ 而人以为存证目录已受保护。
194
+ 2. **零命中的 sealed 规则必须在报告里告警**。路径写错、目录改名都会让它变成一条不干活的护身符。
195
+
196
+ `sealed` 不允许通过 `disposition=sealed` 声明(解析器会 fail closed 报「未知 disposition」):
197
+ 能改文件的目录不需要它,需要它的目录改不了文件。
198
+
199
+ ---
200
+
201
+ ### ignore 必须带 why(2026-10-26)
202
+
203
+ 没有 `why` 的豁免就是洗白:读者看不到「为什么这条不该核」,只能选择相信。所以 `ignore` 的每一项都必须有
204
+ 可读的 `why`(至少 12 字,占位符 `TODO`/`n/a` 不接受),缺了就是配置层致命错,运行直接中止。
205
+ `note` 字段已停用(报错提示改名),`why` 还会跟着声明进入报告 `note`,不再只活在配置里。
206
+
207
+ 退出来的 27 条里,8 条带「TODO: 修绑定」。试把它们删掉——门禁立刻红出 **9 条真漂移**
208
+ (JS 基准/估算值 vs 硬件 C addon 基线),说明这 8 条豁免是**在承重的**,不是清理残渣。
209
+ 所以恢复保留、并在 `why` 里标上「待修绑定后必须删除(#106)」;详见 #106。
210
+
211
+ 这条与 `sealed`、`reference` 同一个思路:**不允许无名的例外存在**。
212
+
213
+ ---
214
+
215
+ ### 标准编号不是度量(2026-10-26,issue #101)
216
+
217
+ `FIPS 203/205`、`GB/T 32918/32905`、`IACR 2025/624`、`ePrint 2025/1986` 与「10000/10000 通过率」在文字形状上完全一样。
218
+ 抽取器为此在比值分支前加一道语义拦:贴着数字左侧 24 字符内出现标准名或出版编号前缀,就标 `notMeasurement`,
219
+ 引擎把它单列为 `status: reference`,**不进判定、不进覆盖率分母,但会在报告里单独一行可见**。
220
+
221
+ 两个反向约束:
222
+
223
+ 1. **不静默删掉**。直接丢弃这条数字就变成「报告里没有」,和假绿同宗;必须计数、必须能指出文件与行号。
224
+ 2. **不宽泛匹配**。大窗口找 `NIST` 会误杀「NIST 测试集通过 10/10」——那是真声明。所以前缀必须锚定在数字左侧、
225
+ 中间只允许空白与点号,并有专门的用例盯住这个假阴性。
226
+
227
+ ---
228
+
229
+ ### 分母只能有一个来源(2026-10-26,issue #102)
230
+
231
+ 覆盖率的分母不再现场重算,只取 `counts.codeArtifactClaims`,而它直接等于进入判定的条数(`stats.claims`)。
232
+ `dispositionCounts` 只统计作者表过态的声明,`ignored` 与 `denied` 各自单列——否则被拒绑的那条会以默认值
233
+ 混进 `code-artifact` 桶,报告上出现「处置分类 code-artifact 247」但「核验率分母 246」。
234
+
235
+ `allJudgedClaims = 进入判定 + 已分类 + 已拒绑`:拒绑也是判定结论,只是不绑定,所以它留在「全部受判」的分母里,
236
+ 但不属于任何一个作者分类。
237
+
238
+ `history.jsonl` 随之升到 `schema 2`:state map 的键换成 `stableKey`。**旧记录不迁移**——行号制的旧记录没有保存上下文,
239
+ 任何启发式映射都会在多条声明共用 key 时误判,还会把「插了一行」读成迁移。宁可让比较基准断一次,也不制造假迁移。
240
+
241
+ ### 扫描崩溃必须变红(2026-10-26,issue #95)
242
+
243
+ 抽取层出异常时,原来的行为是把这个文件记进 `docScanErrors`,然后**继续**:报告少算了一整份文档的声明,却仍然显示绿色。这比绑错更严重 —— 绑错至少看得见,崩溃是静默漏报。实测过的形态:两个文档的项目里 `b.md` 读取失败,`exit 0`、`gate.failed=false`、`b.md` 贡献 0 条,而 `b.md` 里正好有一条本应触发 drift 的声明 —— 崩溃不仅藏了文件,还藏了一次红灯。
244
+
245
+ 现在的规则:
246
+
247
+ | 情况 | 默认 | 说明 |
248
+ |---|---|---|
249
+ | 处置声明写错 | **中止** | 作者错误,任何参数下都不容忍 |
250
+ | 读取/抽取崩溃 | **中止**,exit 1 | 报错里点名文件,并提示逃生阀 |
251
+ | 超过 `maxFileBytes` | 跳过 | 有意为之,不计入失败 |
252
+
253
+ 逃生阀是 `--allow-scan-errors`:显式承认"这些文件我认了",报告顶部会列出被跳过的文件与原因,`meta.skippedFiles[]` 逐条记录 `{file, reason, bytes, limit, tolerated}`。**没有静默跳过这条路** —— 要么红,要么白纸黑字写清跳了谁。
254
+
255
+ `too_large` 保持非致命是有意的:它不改变"这个文件被读过"的事实,只是拒绝解析超大文件,把它也算成崩溃会逼着人调大上限来换绿灯。
256
+
257
+ ## 六、边界(不做什么)
258
+
259
+ - **不调 git**——声明绑定的是文件当前状态,不追溯「这个数字在哪个提交改的」(那是 crypto-time-machine 的活)
260
+ - **不自动修文档**——只报问题,不改内容。verifact 是指纹比对,不是内容审查
261
+ - **`unbound` 不等于说谎**——缺证据只交人工,不进门禁
262
+ - **绑定阈值是经验值**——`minScore: 0.45` 和容差按 fibemate 项目调的,换仓库要重新调
263
+ - **无出站网络**——全部扫描和比对在本机完成;`serve` 起的看板只监听 `127.0.0.1:8787`
264
+ - **写入边界**——只写 `--out` 指定的路径和 `.verifact/` 缓存目录;扫描显式跳过 `.git/`、`node_modules/`
265
+
266
+ ---
267
+
268
+ ## 七、在 CI 里的用法
269
+
270
+ 主仓 `sentinel.yml`:
271
+
272
+ ```yaml
273
+ - name: Run verifact
274
+ working-directory: .sentinel-tools/verifact
275
+ run: node bin/verifact.js verify --docs ../../fibemate/www/docs --fail-on drift --no-cache
276
+ ```
277
+
278
+ - 双仓 checkout(主仓 + fibemate-tools)
279
+ - `--no-cache` 保证每次全量,不吃缓存红利
280
+ - `--fail-on drift` 把红锁定在唯一一种该红的判定上
281
+
282
+ 比成功,CI 绿;比出 drift,CI 红——**不让你合**。
283
+
284
+ ---
285
+
286
+ ## 八、实测数据(2026-09-27)
287
+
288
+ 对 fibemate 全仓 + 待发布文章 + benchmark 产出物跑全量:
289
+
290
+ ```
291
+ 文档 341 份 产出物 5369 条 声明 2251 条
292
+ verified 145 drift 24 ambiguous 227 unbound 1836
293
+ 引用型 19(存在 17 / 缺失 2) 另有 17 条声明经 ignore 规则收敛
294
+ 核验率 7.2%
295
+ 耗时 4.1s(workers=6,冷缓存)
296
+ ```
297
+
298
+ **核验率 7.2% 是诚实的上限**:能自动核验的前提是产出物机器可读。大多数声明(叙事、对比、非数值)没有对应格式。
299
+
300
+ **工具的作用是守住有依据的那部分,不是假装能核全部。**
301
+
302
+ ---
303
+
304
+ ## 九、抓到过什么
305
+
306
+ 投产后实际拦下的:
307
+
308
+ 1. **头条数字无出处**——`p95=78.5ms` 出现在主仓 README 和官网 pqc-readiness、FAQ、architecture 四处,产出物里无依据
309
+ 2. **15-31x 性能声明的底数漂移**——`PROGRESS.md` 的 103µs 对不上产出物的 262μs,倍数声明跟着动摇
310
+ 3. **TLA+ 不变式 3 vs 7**——发布物和模型文件各说各话
311
+ 4. **lg-079 引用缺失**——两个文档副本指向同一份本地查不到的时间戳实体
312
+
313
+ 用语线索单独说:抽取器顺带查绝对化措辞,今天的 high 级 5 条:「全球唯一」「绝对安全」「零风险」。这类词在任何审计语境都站不住。
314
+
315
+ ---
316
+
317
+ ## 十、为什么要公开这份设计
318
+
319
+ **这个问题不限于 fibemate。**
320
+
321
+ 任何有硬数字的项目——性能基准、测试计数、状态数、文件行数——都可能遇到「文档写于某时刻、产出物在另一时刻」的漂移。
322
+
323
+ **「不把没证据当错」的六态判定**是一种设计立场:
324
+ - 比「一切不匹配皆失败」的工具更实用
325
+ - 比「只在明显错误时才报」的工具更主动
326
+ - 把「机器能做的」和「必须交人做的」明确分开
327
+
328
+ 如果这份设计对别的项目有帮助——或者你觉得有改进空间——欢迎在 issues 讨论。
329
+
330
+ ---
331
+
332
+ ## 参考
333
+
334
+ - 工具:github.com/LennonHaha/fibemate-tools → `verifact/`
335
+ - 回归测试:19 项(`node test/run.js`)
336
+ - CI 门禁:fibemate 主仓 `.github/workflows/sentinel.yml`
337
+ - 配置样例:`verifact/verifact.json.example`