@aster-cloud/aster-lang-ts 1.0.27 → 1.0.29
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/dist/scripts/generate_error_codes.js +49 -6
- package/dist/scripts/generate_error_codes.js.map +1 -1
- package/dist/src/ai/providers/openai.d.ts +10 -0
- package/dist/src/ai/providers/openai.d.ts.map +1 -1
- package/dist/src/ai/providers/openai.js +4 -0
- package/dist/src/ai/providers/openai.js.map +1 -1
- package/dist/src/config/lexicons/regex-guard.d.ts.map +1 -1
- package/dist/src/config/lexicons/regex-guard.js +128 -0
- package/dist/src/config/lexicons/regex-guard.js.map +1 -1
- package/dist/src/core/interpreter.d.ts.map +1 -1
- package/dist/src/core/interpreter.js +221 -27
- package/dist/src/core/interpreter.js.map +1 -1
- package/dist/src/cst/cst_builder.js +0 -14
- package/dist/src/cst/cst_builder.js.map +1 -1
- package/dist/src/cst/cst_printer.d.ts.map +1 -1
- package/dist/src/cst/cst_printer.js +20 -3
- package/dist/src/cst/cst_printer.js.map +1 -1
- package/dist/src/diagnostics/error_codes.d.ts +3 -3
- package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
- package/dist/src/diagnostics/error_codes.js +509 -78
- package/dist/src/diagnostics/error_codes.js.map +1 -1
- package/dist/src/formatter.d.ts +7 -0
- package/dist/src/formatter.d.ts.map +1 -1
- package/dist/src/formatter.js +175 -5
- package/dist/src/formatter.js.map +1 -1
- package/dist/src/frontend/canonicalizer.d.ts.map +1 -1
- package/dist/src/frontend/canonicalizer.js +47 -6
- package/dist/src/frontend/canonicalizer.js.map +1 -1
- package/dist/src/frontend/lexer.d.ts +2 -1
- package/dist/src/frontend/lexer.d.ts.map +1 -1
- package/dist/src/frontend/lexer.js +10 -3
- package/dist/src/frontend/lexer.js.map +1 -1
- package/dist/src/frontend/transformers.d.ts.map +1 -1
- package/dist/src/frontend/transformers.js +50 -3
- package/dist/src/frontend/transformers.js.map +1 -1
- package/dist/src/lower_to_core.js +101 -4
- package/dist/src/lower_to_core.js.map +1 -1
- package/dist/src/lsp/canonicalize-options.d.ts +26 -0
- package/dist/src/lsp/canonicalize-options.d.ts.map +1 -0
- package/dist/src/lsp/canonicalize-options.js +14 -0
- package/dist/src/lsp/canonicalize-options.js.map +1 -0
- package/dist/src/lsp/completion.d.ts.map +1 -1
- package/dist/src/lsp/completion.js +22 -4
- package/dist/src/lsp/completion.js.map +1 -1
- package/dist/src/lsp/server.js +29 -1
- package/dist/src/lsp/server.js.map +1 -1
- package/dist/src/lsp/tenant-init.d.ts +55 -0
- package/dist/src/lsp/tenant-init.d.ts.map +1 -0
- package/dist/src/lsp/tenant-init.js +88 -0
- package/dist/src/lsp/tenant-init.js.map +1 -0
- package/dist/src/mapping/candidate-generator.d.ts +84 -0
- package/dist/src/mapping/candidate-generator.d.ts.map +1 -0
- package/dist/src/mapping/candidate-generator.js +114 -0
- package/dist/src/mapping/candidate-generator.js.map +1 -0
- package/dist/src/mapping/entity-proposer.d.ts +85 -0
- package/dist/src/mapping/entity-proposer.d.ts.map +1 -0
- package/dist/src/mapping/entity-proposer.js +162 -0
- package/dist/src/mapping/entity-proposer.js.map +1 -0
- package/dist/src/mapping/mapping-ir.d.ts +85 -0
- package/dist/src/mapping/mapping-ir.d.ts.map +1 -0
- package/dist/src/mapping/mapping-ir.js +195 -0
- package/dist/src/mapping/mapping-ir.js.map +1 -0
- package/dist/src/mapping/proof-ir.d.ts +125 -0
- package/dist/src/mapping/proof-ir.d.ts.map +1 -0
- package/dist/src/mapping/proof-ir.js +48 -0
- package/dist/src/mapping/proof-ir.js.map +1 -0
- package/dist/src/mapping/quantity-ir.d.ts +93 -0
- package/dist/src/mapping/quantity-ir.d.ts.map +1 -0
- package/dist/src/mapping/quantity-ir.js +151 -0
- package/dist/src/mapping/quantity-ir.js.map +1 -0
- package/dist/src/mapping/source-ir.d.ts +82 -0
- package/dist/src/mapping/source-ir.d.ts.map +1 -0
- package/dist/src/mapping/source-ir.js +231 -0
- package/dist/src/mapping/source-ir.js.map +1 -0
- package/dist/src/nodeid/change-impact.d.ts +77 -0
- package/dist/src/nodeid/change-impact.d.ts.map +1 -0
- package/dist/src/nodeid/change-impact.js +132 -0
- package/dist/src/nodeid/change-impact.js.map +1 -0
- package/dist/src/nodeid/node-id-map.d.ts +69 -0
- package/dist/src/nodeid/node-id-map.d.ts.map +1 -0
- package/dist/src/nodeid/node-id-map.js +193 -0
- package/dist/src/nodeid/node-id-map.js.map +1 -0
- package/dist/src/parser/decl-parser.d.ts.map +1 -1
- package/dist/src/parser/decl-parser.js +10 -4
- package/dist/src/parser/decl-parser.js.map +1 -1
- package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
- package/dist/src/parser/expr-stmt-parser.js +36 -24
- package/dist/src/parser/expr-stmt-parser.js.map +1 -1
- package/dist/src/parser/span-utils.d.ts +25 -0
- package/dist/src/parser/span-utils.d.ts.map +1 -1
- package/dist/src/parser/span-utils.js +43 -0
- package/dist/src/parser/span-utils.js.map +1 -1
- package/dist/src/parser/type-parser.d.ts.map +1 -1
- package/dist/src/parser/type-parser.js +21 -0
- package/dist/src/parser/type-parser.js.map +1 -1
- package/dist/src/typecheck/expression.d.ts.map +1 -1
- package/dist/src/typecheck/expression.js +26 -0
- package/dist/src/typecheck/expression.js.map +1 -1
- package/dist/test/fuzz/fuzz.test.js +62 -2
- package/dist/test/fuzz/fuzz.test.js.map +1 -1
- package/dist/test/unit/call-arity.test.d.ts +2 -0
- package/dist/test/unit/call-arity.test.d.ts.map +1 -0
- package/dist/test/unit/call-arity.test.js +71 -0
- package/dist/test/unit/call-arity.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/canonicalizer.test.js +42 -4
- package/dist/test/unit/canonicalizer/canonicalizer.test.js.map +1 -1
- package/dist/test/unit/canonicalizer/line-preservation.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.js +71 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.js +98 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.js +101 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.js.map +1 -0
- package/dist/test/unit/formatter-redos.test.d.ts +2 -0
- package/dist/test/unit/formatter-redos.test.d.ts.map +1 -0
- package/dist/test/unit/formatter-redos.test.js +196 -0
- package/dist/test/unit/formatter-redos.test.js.map +1 -0
- package/dist/test/unit/interpreter/interpreter.test.js +7 -2
- package/dist/test/unit/interpreter/interpreter.test.js.map +1 -1
- package/dist/test/unit/lexer/bom.test.d.ts +2 -0
- package/dist/test/unit/lexer/bom.test.d.ts.map +1 -0
- package/dist/test/unit/lexer/bom.test.js +49 -0
- package/dist/test/unit/lexer/bom.test.js.map +1 -0
- package/dist/test/unit/list-get-bounds.test.d.ts +2 -0
- package/dist/test/unit/list-get-bounds.test.d.ts.map +1 -0
- package/dist/test/unit/list-get-bounds.test.js +66 -0
- package/dist/test/unit/list-get-bounds.test.js.map +1 -0
- package/dist/test/unit/long-bigint-exact.test.d.ts +2 -0
- package/dist/test/unit/long-bigint-exact.test.d.ts.map +1 -0
- package/dist/test/unit/long-bigint-exact.test.js +79 -0
- package/dist/test/unit/long-bigint-exact.test.js.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.d.ts +2 -0
- package/dist/test/unit/lsp/completion-lexicon.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js +83 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts +2 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.js +187 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.js.map +1 -0
- package/dist/test/unit/map-builtins.test.js +163 -3
- package/dist/test/unit/map-builtins.test.js.map +1 -1
- package/dist/test/unit/map-structural-equality.test.d.ts +2 -0
- package/dist/test/unit/map-structural-equality.test.d.ts.map +1 -0
- package/dist/test/unit/map-structural-equality.test.js +64 -0
- package/dist/test/unit/map-structural-equality.test.js.map +1 -0
- package/dist/test/unit/map-type-guard.test.js +8 -5
- package/dist/test/unit/map-type-guard.test.js.map +1 -1
- package/dist/test/unit/mapping/candidate-generator.test.d.ts +2 -0
- package/dist/test/unit/mapping/candidate-generator.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/candidate-generator.test.js +177 -0
- package/dist/test/unit/mapping/candidate-generator.test.js.map +1 -0
- package/dist/test/unit/mapping/entity-proposer.test.d.ts +2 -0
- package/dist/test/unit/mapping/entity-proposer.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/entity-proposer.test.js +168 -0
- package/dist/test/unit/mapping/entity-proposer.test.js.map +1 -0
- package/dist/test/unit/mapping/mapping-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/mapping-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/mapping-ir.test.js +144 -0
- package/dist/test/unit/mapping/mapping-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/proof-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/proof-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/proof-ir.test.js +119 -0
- package/dist/test/unit/mapping/proof-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/quantity-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/quantity-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/quantity-ir.test.js +144 -0
- package/dist/test/unit/mapping/quantity-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.d.ts +2 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.js +182 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.js.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.d.ts +2 -0
- package/dist/test/unit/mapping/redos-budget.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.js +168 -0
- package/dist/test/unit/mapping/redos-budget.test.js.map +1 -0
- package/dist/test/unit/mapping/source-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/source-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/source-ir.test.js +113 -0
- package/dist/test/unit/mapping/source-ir.test.js.map +1 -0
- package/dist/test/unit/nodeid/change-impact.test.d.ts +2 -0
- package/dist/test/unit/nodeid/change-impact.test.d.ts.map +1 -0
- package/dist/test/unit/nodeid/change-impact.test.js +152 -0
- package/dist/test/unit/nodeid/change-impact.test.js.map +1 -0
- package/dist/test/unit/nodeid/rename-declaration.test.d.ts +2 -0
- package/dist/test/unit/nodeid/rename-declaration.test.d.ts.map +1 -0
- package/dist/test/unit/nodeid/rename-declaration.test.js +75 -0
- package/dist/test/unit/nodeid/rename-declaration.test.js.map +1 -0
- package/dist/test/unit/parse-type-recursion.test.d.ts +2 -0
- package/dist/test/unit/parse-type-recursion.test.d.ts.map +1 -0
- package/dist/test/unit/parse-type-recursion.test.js +58 -0
- package/dist/test/unit/parse-type-recursion.test.js.map +1 -0
- package/dist/test/unit/regex-guard.test.js +64 -0
- package/dist/test/unit/regex-guard.test.js.map +1 -1
- package/dist/test/unit/strict-int-coercion.test.d.ts +2 -0
- package/dist/test/unit/strict-int-coercion.test.d.ts.map +1 -0
- package/dist/test/unit/strict-int-coercion.test.js +96 -0
- package/dist/test/unit/strict-int-coercion.test.js.map +1 -0
- package/dist/test/unit/ufcs-receiver.test.d.ts +2 -0
- package/dist/test/unit/ufcs-receiver.test.d.ts.map +1 -0
- package/dist/test/unit/ufcs-receiver.test.js +75 -0
- package/dist/test/unit/ufcs-receiver.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { extractQuantities } from '../../../src/mapping/quantity-ir.js';
|
|
4
|
+
import { parseSourceIr, nodeAtOffset } from '../../../src/mapping/source-ir.js';
|
|
5
|
+
import { verifyMapping } from '../../../src/mapping/mapping-ir.js';
|
|
6
|
+
/**
|
|
7
|
+
* QuantityIR(ADR 0037 §2/§10.5)—— 从人类文档机械抽取数量实体。
|
|
8
|
+
*
|
|
9
|
+
* <p>核心主张:**Quantity 有形态特征、可机械抽取;Entity 没有、需要 LLM**。
|
|
10
|
+
* 本测试证明前半句,并用类型系统把后半句的边界钉住。
|
|
11
|
+
*/
|
|
12
|
+
const POLICY = [
|
|
13
|
+
'# 付款审批政策', '',
|
|
14
|
+
'## 阈值', '',
|
|
15
|
+
'单笔付款超过 $10,000 时需要财务经理审批。',
|
|
16
|
+
'低于 $10,000 的付款由部门主管批准即可。', '',
|
|
17
|
+
'紧急付款上限为 $50,000,须在 24 小时内补批。', '',
|
|
18
|
+
'## 手续费', '',
|
|
19
|
+
'跨境付款收取 1.5% 手续费,最低 $25。', '',
|
|
20
|
+
'## 生效日期', '',
|
|
21
|
+
'本政策自 2026-01-01 起生效,2026-12-31 失效。', '',
|
|
22
|
+
].join('\n');
|
|
23
|
+
describe('QuantityIR — 机械抽取数量实体', () => {
|
|
24
|
+
it('★抽出全部四类数量,且 value 已规范化', () => {
|
|
25
|
+
const qs = extractQuantities(POLICY);
|
|
26
|
+
const byKind = (k) => qs.filter(q => q.kind === k).map(q => q.value);
|
|
27
|
+
assert.deepStrictEqual(byKind('MONEY'), ['10000', '10000', '50000', '25']);
|
|
28
|
+
assert.deepStrictEqual(byKind('PERCENT'), ['1.5']);
|
|
29
|
+
assert.deepStrictEqual(byKind('DURATION'), ['24']);
|
|
30
|
+
assert.deepStrictEqual(byKind('DATE'), ['2026-01-01', '2026-12-31']);
|
|
31
|
+
});
|
|
32
|
+
it('★text 必须逐字节等于文档切片(与 SourceIR 同规则:不改内容)', () => {
|
|
33
|
+
for (const q of extractQuantities(POLICY)) {
|
|
34
|
+
assert.strictEqual(q.text, POLICY.slice(q.span.start, q.span.end), `${q.kind} 的 text 与切片不符 —— 说明做了文本改写。`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
it('★输出按位置升序且互不重叠', () => {
|
|
38
|
+
// ★样本必须选**真会重叠**的输入。POLICY 里四类模式互斥,重叠永不发生,
|
|
39
|
+
// 拿它做样本时把重叠检查整个删掉测试照样绿(实测过)——假门禁。
|
|
40
|
+
// `$1.5%` 会同时匹配 MONEY(`$1.5`) 与 PERCENT(`1.5%`),区间相交。
|
|
41
|
+
for (const doc of ['$1.5% 混合', POLICY]) {
|
|
42
|
+
const qs = extractQuantities(doc);
|
|
43
|
+
for (let i = 1; i < qs.length; i++) {
|
|
44
|
+
assert.ok(qs[i].span.start >= qs[i - 1].span.end, `${qs[i - 1].text} 与 ${qs[i].text} 重叠或乱序(文档 ${JSON.stringify(doc.slice(0, 20))})`
|
|
45
|
+
+ '\n★重叠会让同一段文本被抽成两个候选,映射时互相矛盾。');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
it('`$10,000` 整体抽取,千分位逗号去掉', () => {
|
|
50
|
+
const qs = extractQuantities('付款 $10,000 元');
|
|
51
|
+
assert.strictEqual(qs.length, 1, `应抽出 1 个金额,实际 ${JSON.stringify(qs.map(q => q.text))}`);
|
|
52
|
+
assert.strictEqual(qs[0].text, '$10,000');
|
|
53
|
+
assert.strictEqual(qs[0].value, '10000');
|
|
54
|
+
});
|
|
55
|
+
it('★模式顺序决定重叠时谁胜出:MONEY 先于 PERCENT', () => {
|
|
56
|
+
// ★这条才真正钉住 PATTERNS 的顺序。`$1.5%` 是唯一会让两个模式相交的形态:
|
|
57
|
+
// MONEY 匹配 `$1.5`、PERCENT 匹配 `1.5%`。先声明者占位,后者出局。
|
|
58
|
+
// 把 MONEY 挪到 PERCENT 之后,本条立刻变红。
|
|
59
|
+
// (原先我用 `$10,000` 和 `1.5%` 做「优先级」断言,但那两个输入下
|
|
60
|
+
// 根本没有竞争者——挪动顺序测试照样绿,是假门禁。)
|
|
61
|
+
const qs = extractQuantities('$1.5% 混合');
|
|
62
|
+
assert.strictEqual(qs.length, 1, `重叠时应只保留一个,实际 ${JSON.stringify(qs.map(q => q.text))}`);
|
|
63
|
+
assert.strictEqual(qs[0].kind, 'MONEY', 'MONEY 在 PATTERNS 中先声明,应胜出。');
|
|
64
|
+
assert.strictEqual(qs[0].text, '$1.5');
|
|
65
|
+
});
|
|
66
|
+
it('★超安全整数的金额不得丢精度', () => {
|
|
67
|
+
// 一旦过一次 JS number 就变 …992(本仓实测踩过)。
|
|
68
|
+
const qs = extractQuantities('上限 $9007199254740993 元');
|
|
69
|
+
assert.strictEqual(qs[0].value, '9007199254740993', '大额金额精度丢失 —— 说明中途经过了 JS number。');
|
|
70
|
+
});
|
|
71
|
+
it('DATE 保持原串,不转时间戳', () => {
|
|
72
|
+
// 转时间戳会引入时区,而时区不是原文里的信息。
|
|
73
|
+
const qs = extractQuantities('生效日 2026-01-01。');
|
|
74
|
+
assert.strictEqual(qs[0].value, '2026-01-01');
|
|
75
|
+
assert.strictEqual(qs[0].unit, undefined);
|
|
76
|
+
});
|
|
77
|
+
it('★形态像但规范化不出来的,不抽取(绝不编造)', () => {
|
|
78
|
+
// `$ 待定` 与 `2026-1-1` 连**模式**都匹配不上,测不出 normalize 那道门。
|
|
79
|
+
// 真正能触发 normalize 返回 undefined 的是:模式匹配成功、但规范化失败。
|
|
80
|
+
// `$0,00` 匹配 MONEY 模式(`$` + 数字 + 逗号 + 数字),去逗号后是 `000`,
|
|
81
|
+
// canonicalDecimal 能处理 → 仍会抽出。故这里用**非法日期**:模式要求
|
|
82
|
+
// \d{4}-\d{2}-\d{2},`2026-13-45` 匹配得上但语义非法。
|
|
83
|
+
const weird = extractQuantities('日期 2026-13-45 和 2026-01-01。');
|
|
84
|
+
const dates = weird.filter(q => q.kind === 'DATE').map(q => q.text);
|
|
85
|
+
// ★如实记录当前行为:本模块**不做语义校验**(13 月 45 日照抽)。
|
|
86
|
+
// 它只保证「抽出来的东西形态合法且 value 可规范化」,语义合法性由
|
|
87
|
+
// 下游 verifier 或人判定 —— 这与 MappingIR 的分工一致。
|
|
88
|
+
assert.deepStrictEqual(dates, ['2026-13-45', '2026-01-01'], '本模块只按形态抽取,不做日期语义校验(该判断属下游)。');
|
|
89
|
+
// 而**模式都匹配不上**的,自然不产出:
|
|
90
|
+
assert.deepStrictEqual(extractQuantities('价格 $ 待定'), []);
|
|
91
|
+
});
|
|
92
|
+
it('★全链路:SourceIR 定位 → Quantity 抽取 → MappingIR 判定', () => {
|
|
93
|
+
// 这是 Quantity 层存在的理由:让人类文档里的数字能接进验证链。
|
|
94
|
+
const ir = parseSourceIr(POLICY);
|
|
95
|
+
const money = extractQuantities(POLICY).filter(q => q.kind === 'MONEY');
|
|
96
|
+
assert.ok(money.length > 0, '应抽出金额。');
|
|
97
|
+
for (const q of money) {
|
|
98
|
+
// 每个金额都必须落在某个具体结构节点里
|
|
99
|
+
const host = nodeAtOffset(ir, q.span.start);
|
|
100
|
+
assert.ok(host !== undefined && host.kind !== 'DOCUMENT', `${q.text} 未落在具体结构节点内(实际 ${host?.kind})。`);
|
|
101
|
+
// 且能被 MappingIR 验过对应的 Decimal 节点
|
|
102
|
+
const r = verifyMapping({ span: q.span, text: q.text, nodeId: '$.threshold' }, () => ({ kind: 'Decimal', value: q.value }));
|
|
103
|
+
assert.strictEqual(r.verdict, 'VERIFIED', `${q.text} 应能验过 Decimal(${q.value})。实际 ${r.verdict}:${r.reason}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
it('★Entity 不由本模块抽取(边界写进类型系统)', () => {
|
|
107
|
+
// 「财务经理」「部门主管」在文档里,但**不该**出现在 Quantity 输出里——
|
|
108
|
+
// 它们没有形态特征,只能由 LLM/人提出 EntityCandidate。
|
|
109
|
+
// 若哪天有人给本模块加了「角色识别」的正则,这条会变红。
|
|
110
|
+
const texts = extractQuantities(POLICY).map(q => q.text);
|
|
111
|
+
for (const role of ['财务经理', '部门主管']) {
|
|
112
|
+
assert.ok(!texts.some(t => t.includes(role)), `${role} 出现在 Quantity 输出里 —— Entity 应由 LLM 提出,本模块不得猜测。`);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
it('★ReDoS 守卫:长数字串不得触发二次回溯', () => {
|
|
116
|
+
// 本模块吃的是**人类文档**——攻击者可控的输入。
|
|
117
|
+
// 没有 `(?<![\d.])` 左锚时,`\d+` 会在每个数字位置重新贪婪扫描再回退,
|
|
118
|
+
// 实测呈二次增长:5000→36ms、10000→144ms、20000→576ms、40000→2304ms。
|
|
119
|
+
// 一份构造过的文档就能把抽取线程钉死。
|
|
120
|
+
const evil = '$' + '1'.repeat(40000);
|
|
121
|
+
const t = Date.now();
|
|
122
|
+
extractQuantities(evil);
|
|
123
|
+
const ms = Date.now() - t;
|
|
124
|
+
assert.ok(ms < 500, `40000 长度的数字串耗时 ${ms}ms —— 应 <500ms。`
|
|
125
|
+
+ '\n★超时说明 PERCENT/DURATION 的左锚 `(?<![\\d.])` 被去掉了,二次回溯回来了。');
|
|
126
|
+
});
|
|
127
|
+
it('★加锚后语义不变(左锚是 ReDoS 修复,不得改变匹配结果)', () => {
|
|
128
|
+
// 反向守卫:若有人为了"更快"把锚点改成别的写法而改变了语义,这条会红。
|
|
129
|
+
const cases = [
|
|
130
|
+
['抽 1.5% 起', ['1.5%']],
|
|
131
|
+
['30 天内', ['30 天']],
|
|
132
|
+
['1.5%和24小时', ['1.5%', '24小时']],
|
|
133
|
+
['0.5天', ['0.5天']],
|
|
134
|
+
['100%', ['100%']],
|
|
135
|
+
];
|
|
136
|
+
for (const [doc, expected] of cases) {
|
|
137
|
+
const got = extractQuantities(doc)
|
|
138
|
+
.filter(q => q.kind === 'PERCENT' || q.kind === 'DURATION')
|
|
139
|
+
.map(q => q.text);
|
|
140
|
+
assert.deepStrictEqual(got, [...expected], `文档 ${JSON.stringify(doc)} 的匹配结果变了`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=quantity-ir.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quantity-ir.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/quantity-ir.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quantity-redos-property.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/quantity-redos-property.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import * as fc from 'fast-check';
|
|
4
|
+
import { extractQuantities } from '../../../src/mapping/quantity-ir.js';
|
|
5
|
+
/**
|
|
6
|
+
* ReDoS 修复的**穷举式**验证(property-based)。
|
|
7
|
+
*
|
|
8
|
+
* <h2>为什么需要这一层</h2>
|
|
9
|
+
*
|
|
10
|
+
* `quantity-ir.test.ts` 里的语义守卫用的是**人工挑的 9 组样本**。那只能证明
|
|
11
|
+
* 「这 9 组没变」,证明不了「所有输入都没变」——而 `(?<![\d.])` 左锚是**改了
|
|
12
|
+
* 正则**的修复,语义等价性需要覆盖面远大于人工样本的证据。
|
|
13
|
+
*
|
|
14
|
+
* <p>本文件用 fast-check 生成成千上万组输入,逐条比对**加锚前后**的匹配结果。
|
|
15
|
+
*
|
|
16
|
+
* <h2>★放在 test/unit/ 而不是 test/property/</h2>
|
|
17
|
+
*
|
|
18
|
+
* `package.json` 的 `test:unit:run` 只扫 `dist/test/unit/**` 与
|
|
19
|
+
* `dist/test/type-checker/**`——**`test/property/` 不在 CI 里**。
|
|
20
|
+
* 把安全回归放进一个不跑的目录等于没有守卫(本仓反复记录过这类假门禁)。
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* 加锚**之前**的完整模式表——★必须**四条全列**,且顺序与实现一致。
|
|
24
|
+
*
|
|
25
|
+
* <p>★这里踩过一个坑,值得记下来:我最初只列了 PERCENT/DURATION 两条(因为只
|
|
26
|
+
* 有这两条加了锚),结果 property 立刻报出反例 `"$0%"`——但那**不是实现的
|
|
27
|
+
* 问题,是这个基准函数写错了**。
|
|
28
|
+
*
|
|
29
|
+
* <p>`$0%` 里 MONEY 先声明、先占位 `[0,2)`,PERCENT 的 `[1,3)` 与之重叠被丢。
|
|
30
|
+
* 少列 MONEY 的基准函数看不到这次占位,于是以为 PERCENT 应该被保留。
|
|
31
|
+
*
|
|
32
|
+
* <p>★教训:对照基准必须复刻**整条流水线**,漏掉任一环节,差异会被归咎到
|
|
33
|
+
* 被测代码头上(本仓记过的「量具自身的缺陷」)。
|
|
34
|
+
*/
|
|
35
|
+
const LEGACY = [
|
|
36
|
+
/[$€£¥]\s?\d[\d,]*(?:\.\d+)?/g, // MONEY
|
|
37
|
+
/\d{4}-\d{2}-\d{2}/g, // DATE
|
|
38
|
+
/\d+(?:\.\d+)?\s?%/g, // PERCENT(未加锚)
|
|
39
|
+
/\d+(?:\.\d+)?\s?(?:小时|分钟|天|秒|hours?|minutes?|days?|seconds?)/g, // DURATION(未加锚)
|
|
40
|
+
];
|
|
41
|
+
/** 用旧模式跑完整流水线(含「先声明者占位」的去重),返回 [文本, 起点]。 */
|
|
42
|
+
function legacyExtract(doc) {
|
|
43
|
+
const claimed = [];
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const re of LEGACY) {
|
|
46
|
+
re.lastIndex = 0;
|
|
47
|
+
for (let m = re.exec(doc); m !== null; m = re.exec(doc)) {
|
|
48
|
+
const start = m.index;
|
|
49
|
+
const end = start + m[0].length;
|
|
50
|
+
if (claimed.some(c => start < c.end && end > c.start))
|
|
51
|
+
continue;
|
|
52
|
+
claimed.push({ start, end });
|
|
53
|
+
out.push({ text: m[0], start });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out.sort((a, b) => a.start - b.start);
|
|
57
|
+
}
|
|
58
|
+
/** 现行实现的抽取结果,口径与 {@link legacyExtract} 对齐。 */
|
|
59
|
+
function currentExtract(doc) {
|
|
60
|
+
return extractQuantities(doc).map(q => ({ text: q.text, start: q.span.start }));
|
|
61
|
+
}
|
|
62
|
+
const key = (x) => `${x.start}:${x.text}`;
|
|
63
|
+
/**
|
|
64
|
+
* 偏向「容易触发差异」的生成器:数字、小数点、单位、空白、货币符号的任意拼接。
|
|
65
|
+
*
|
|
66
|
+
* <p>★纯随机字符串几乎不会命中这两个模式,等于没测。这里用**结构化片段**
|
|
67
|
+
* 拼装,让生成的输入大概率包含 `数字+单位` 形态。
|
|
68
|
+
*/
|
|
69
|
+
const FRAGMENT = fc.oneof(fc.constantFrom('0', '1', '9', '10', '100', '1.5', '0.5', '12.75', '00', '1.'), fc.constantFrom('%', '天', '小时', '分钟', '秒', 'days', 'day', 'hours', 'h'), fc.constantFrom('', ' ', ' ', '\t', '\n'), fc.constantFrom('$', ',', '。', 'x', '-', '.'));
|
|
70
|
+
const DOC = fc.array(FRAGMENT, { minLength: 0, maxLength: 24 }).map(xs => xs.join(''));
|
|
71
|
+
describe('QuantityIR ReDoS 修复 — property-based 验证', () => {
|
|
72
|
+
it('★加锚只会**减少**匹配,且减掉的必然是「从数字中间起跑」的伪片段', () => {
|
|
73
|
+
// ★这条修正了我先前的一个错误论断。
|
|
74
|
+
//
|
|
75
|
+
// 我原先说「加锚语义完全不变」,依据是 9 组人工样本。property 测试
|
|
76
|
+
// 立刻给出反例:`"1.51.5%"`
|
|
77
|
+
// 旧模式 → ["51.5%"] ← 从第 4 位起跑,抽出一个**凭空造出来的值**
|
|
78
|
+
// 新模式 → [] ← 左锚看到前面是 ".",拒绝起跑
|
|
79
|
+
//
|
|
80
|
+
// 进一步实测旧模式在畸形数字上的表现:
|
|
81
|
+
// "12.34.56%" → ["34.56%"]
|
|
82
|
+
// "1..5%" → ["5%"]
|
|
83
|
+
// "..5%" → ["5%"]
|
|
84
|
+
//
|
|
85
|
+
// ★这些都是**错的**:`12.34.56%` 不是合法数量,抽出 `34.56%` 等于伪造
|
|
86
|
+
// 一个值,而它随后会被 MappingIR 拿去与 IR 节点比对。所以左锚不只是
|
|
87
|
+
// ReDoS 修复,**它顺带修掉了一个「静默伪造数量」的缺陷**。
|
|
88
|
+
//
|
|
89
|
+
// 故正确的性质不是「完全相等」,而是:
|
|
90
|
+
// ① 新结果是旧结果的**子集**(只减不增,绝不新增匹配)
|
|
91
|
+
// ② 被减掉的那些,起跑位置前面必然是数字或小数点(= 从数字中间起跑)
|
|
92
|
+
fc.assert(fc.property(DOC, (doc) => {
|
|
93
|
+
const now = currentExtract(doc);
|
|
94
|
+
const before = legacyExtract(doc);
|
|
95
|
+
const beforeKeys = new Set(before.map(key));
|
|
96
|
+
const nowKeys = new Set(now.map(key));
|
|
97
|
+
// ① 只减不增
|
|
98
|
+
for (const x of now) {
|
|
99
|
+
assert.ok(beforeKeys.has(key(x)), `新增了旧模式没有的匹配 ${JSON.stringify(x)} —— 加锚不该产生新匹配。`
|
|
100
|
+
+ `\n输入 ${JSON.stringify(doc)}`);
|
|
101
|
+
}
|
|
102
|
+
// ② 减掉的必然是「从数字中间起跑」的伪片段
|
|
103
|
+
for (const x of before) {
|
|
104
|
+
if (nowKeys.has(key(x)))
|
|
105
|
+
continue;
|
|
106
|
+
const prev = x.start > 0 ? doc[x.start - 1] : '';
|
|
107
|
+
assert.ok(/[\d.]/.test(prev), `被减掉的 ${JSON.stringify(x)} 前面是 ${JSON.stringify(prev)},`
|
|
108
|
+
+ '不是数字/小数点 —— 说明锚点误伤了合法匹配。'
|
|
109
|
+
+ `\n输入 ${JSON.stringify(doc)}`);
|
|
110
|
+
}
|
|
111
|
+
}), { numRuns: 3000 });
|
|
112
|
+
});
|
|
113
|
+
it('★锚点必须真的在起作用 —— 存在输入使新旧结果不同(反向守卫)', () => {
|
|
114
|
+
// ★这条是**本文件最重要的一条**,它补的是我自己写的前一条的漏洞。
|
|
115
|
+
//
|
|
116
|
+
// 前一条断言的是「只减不增」+「减掉的是伪片段」。变异验证(拆掉两处
|
|
117
|
+
// 锚点重跑)发现它**依然全绿**——因为锚点没了之后 now === before,
|
|
118
|
+
// 两个方向都**空洞地成立**。我只断言了变化的**方向**,没断言变化
|
|
119
|
+
// **发生过**。这正是本仓记过的「测试锁形状不锁内容」。
|
|
120
|
+
//
|
|
121
|
+
// 故补上这条:必须存在至少一个输入,新旧结果确有差异。
|
|
122
|
+
//
|
|
123
|
+
// ★★这条的**第一版仍然是假绿**,由独立审查者用变异找出来:
|
|
124
|
+
//
|
|
125
|
+
// 原写法是 `witnesses.filter(...).length > 0`——一个**逻辑或**。
|
|
126
|
+
// 把 DURATION 的锚 `(?<![\d.])` 削弱成 `(?<!\d)`(精确复活「静默伪造
|
|
127
|
+
// 数量」缺陷:`1.2.3 小时` → 伪造出 `2.3 小时`),**11/11 依然全绿**,
|
|
128
|
+
// 因为 `1.51.5%` 走的是 PERCENT、锚完好、仍有差异 → OR 条件满足。
|
|
129
|
+
//
|
|
130
|
+
// ★**一个还活着的锚点替所有其他锚点背书**。更讽刺的是
|
|
131
|
+
// `1.2.3 小时` 就在见证集里,但它在变异后与基准相等、对 OR 贡献 0,
|
|
132
|
+
// 于是无人察觉。
|
|
133
|
+
//
|
|
134
|
+
// 修法:**逐 kind 分别钉死**,不许任何一条搭别人的便车。
|
|
135
|
+
const WITNESSES = [
|
|
136
|
+
// [输入, 现行应抽出的, 旧模式(无锚)会抽出的伪造值]
|
|
137
|
+
['1.51.5%', [], ['51.5%']],
|
|
138
|
+
['12.34.56%', [], ['34.56%']],
|
|
139
|
+
['1..5%', [], ['5%']],
|
|
140
|
+
['..5%', [], ['5%']],
|
|
141
|
+
['1.2.3 小时', [], ['2.3 小时']], // ← DURATION,必须单独钉
|
|
142
|
+
['12.34.56 days', [], ['34.56 days']], // ← DURATION 英文单位,同样单独钉
|
|
143
|
+
['1..5 hours', [], ['5 hours']],
|
|
144
|
+
];
|
|
145
|
+
for (const [doc, expectedNow, expectedLegacy] of WITNESSES) {
|
|
146
|
+
// ① 现行实现**不得**抽出任何伪造数量
|
|
147
|
+
assert.deepStrictEqual(currentExtract(doc).map(x => x.text), [...expectedNow], `${JSON.stringify(doc)} 不是合法数量,不应抽出任何值。\n`
|
|
148
|
+
+ '★抽出了值说明该 kind 的左锚 `(?<![\\d.])` 失效或被削弱'
|
|
149
|
+
+ '(例如改成 `(?<!\\d)` 就只挡数字、不挡小数点)。');
|
|
150
|
+
// ② 基准确实会抽出伪造值——否则上面那条是空洞的
|
|
151
|
+
assert.deepStrictEqual(legacyExtract(doc).map(x => x.text), [...expectedLegacy], `基准函数未复现旧行为 —— 对照基准本身失效了。输入 ${JSON.stringify(doc)}`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
it('★任意输入下输出都无重叠、按位置升序(不变式,非样本)', () => {
|
|
155
|
+
fc.assert(fc.property(DOC, (doc) => {
|
|
156
|
+
const qs = extractQuantities(doc);
|
|
157
|
+
for (let i = 1; i < qs.length; i++) {
|
|
158
|
+
assert.ok(qs[i].span.start >= qs[i - 1].span.end, `重叠或乱序:${JSON.stringify(doc)} → ${JSON.stringify(qs.map(q => q.text))}`);
|
|
159
|
+
}
|
|
160
|
+
}), { numRuns: 3000 });
|
|
161
|
+
});
|
|
162
|
+
it('★任意输入下 text 都逐字节等于切片(不变式)', () => {
|
|
163
|
+
fc.assert(fc.property(DOC, (doc) => {
|
|
164
|
+
for (const q of extractQuantities(doc)) {
|
|
165
|
+
assert.strictEqual(q.text, doc.slice(q.span.start, q.span.end));
|
|
166
|
+
}
|
|
167
|
+
}), { numRuns: 3000 });
|
|
168
|
+
});
|
|
169
|
+
it('★纯 ASCII 随机串也不得破坏不变式(补齐结构化生成器的盲区)', () => {
|
|
170
|
+
// 结构化生成器偏向「像数量」的输入,可能漏掉奇怪的字符组合。
|
|
171
|
+
fc.assert(fc.property(fc.string({ minLength: 0, maxLength: 120 }), (doc) => {
|
|
172
|
+
const beforeKeys = new Set(legacyExtract(doc).map(key));
|
|
173
|
+
for (const x of currentExtract(doc)) {
|
|
174
|
+
assert.ok(beforeKeys.has(key(x)), `新增匹配 ${JSON.stringify(x)},输入 ${JSON.stringify(doc)}`);
|
|
175
|
+
}
|
|
176
|
+
for (const q of extractQuantities(doc)) {
|
|
177
|
+
assert.strictEqual(q.text, doc.slice(q.span.start, q.span.end));
|
|
178
|
+
}
|
|
179
|
+
}), { numRuns: 2000 });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=quantity-redos-property.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quantity-redos-property.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/quantity-redos-property.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redos-budget.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/redos-budget.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { extractQuantities } from '../../../src/mapping/quantity-ir.js';
|
|
4
|
+
import { parseSourceIr } from '../../../src/mapping/source-ir.js';
|
|
5
|
+
/**
|
|
6
|
+
* ReDoS **时间预算门禁**(ADR 0037 §12.6)。
|
|
7
|
+
*
|
|
8
|
+
* <h2>★为什么不能只用「绝对毫秒阈值」</h2>
|
|
9
|
+
*
|
|
10
|
+
* `quantity-ir.test.ts` 里已有一条 `ms < 500` 的断言。它能抓住「锚点被删」,
|
|
11
|
+
* 但作为**门禁**有结构性缺陷:
|
|
12
|
+
*
|
|
13
|
+
* <ul>
|
|
14
|
+
* <li>阈值定低 → 共享 CI runner 抖动就红(flaky,最后被人加 skip)</li>
|
|
15
|
+
* <li>阈值定高 → 一个「只慢 3 倍」的新回溯照样溜过去(门禁形同虚设)</li>
|
|
16
|
+
* </ul>
|
|
17
|
+
*
|
|
18
|
+
* <p>★灾难性回溯的特征不是「慢」,是**随输入长度超线性增长**。所以这里量的是
|
|
19
|
+
* <b>增长率</b>而非绝对耗时:输入翻倍,耗时应大致翻倍(线性);若接近翻四倍
|
|
20
|
+
* (二次),就是回溯回来了。增长率对机器速度**不敏感**——慢机器上两个数据点
|
|
21
|
+
* 一起变慢,比值不变。
|
|
22
|
+
*
|
|
23
|
+
* <p>实测(未加锚时)正是二次:5000→36ms、10000→144ms、20000→576ms、
|
|
24
|
+
* 40000→2304ms,每翻倍约 ×4。
|
|
25
|
+
*/
|
|
26
|
+
/** 取多次测量的**最小值**——最小值最接近真实计算量,受调度抖动干扰最小。 */
|
|
27
|
+
function timeOf(fn, repeats = 7) {
|
|
28
|
+
let best = Infinity;
|
|
29
|
+
for (let i = 0; i < repeats; i++) {
|
|
30
|
+
const t = process.hrtime.bigint();
|
|
31
|
+
fn();
|
|
32
|
+
const ms = Number(process.hrtime.bigint() - t) / 1e6;
|
|
33
|
+
if (ms < best)
|
|
34
|
+
best = ms;
|
|
35
|
+
}
|
|
36
|
+
return best;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 断言 `fn` 对长度 n 的输入呈**次二次**增长。
|
|
40
|
+
*
|
|
41
|
+
* @param ratioLimit 输入翻倍时允许的最大耗时倍数。线性=2,二次=4。
|
|
42
|
+
* 取 3.0 作门槛:线性(2)有 50% 余量,二次(4)被稳稳挡住。
|
|
43
|
+
*/
|
|
44
|
+
function assertSubQuadratic(label, build, fn, base, ratioLimit = 3.0) {
|
|
45
|
+
const small = build(base);
|
|
46
|
+
const large = build(base * 2);
|
|
47
|
+
// 预热:让 JIT 稳定下来,否则第一次调用的编译开销会污染比值。
|
|
48
|
+
fn(build(base / 4));
|
|
49
|
+
const tSmall = timeOf(() => { fn(small); });
|
|
50
|
+
const tLarge = timeOf(() => { fn(large); });
|
|
51
|
+
// ★耗时过短时比值噪声极大(0.1ms vs 0.2ms 的比值毫无意义)。
|
|
52
|
+
//
|
|
53
|
+
// ★这里原本写的是 `if (tLarge < 1.0) assert.ok(tLarge < 50)` ——
|
|
54
|
+
// **那是一个恒真断言**:进入分支的前提就是 `tLarge < 1.0`,而 1.0 < 50。
|
|
55
|
+
// 我在注释里写「两条路径都真的断言了东西」,**那句话是错的**。
|
|
56
|
+
// 独立审查者实测:6 个用例里有 5 个**恒走**这条分支,比值断言从不执行。
|
|
57
|
+
//
|
|
58
|
+
// 修法:早退分支改用**紧贴噪声地板**的绝对上界(而非写死的 50ms)。
|
|
59
|
+
// 这样「把耗时从 0.1ms 推到 0.9ms」这类中间地带的回归也会被抓到,
|
|
60
|
+
// 而不是留下 82×〜8621× 的余量。
|
|
61
|
+
const NOISE_FLOOR_MS = 1.0;
|
|
62
|
+
if (tLarge < NOISE_FLOOR_MS) {
|
|
63
|
+
assert.ok(tLarge < NOISE_FLOOR_MS, `${label}:耗时 ${tLarge.toFixed(3)}ms —— 应 <${NOISE_FLOOR_MS}ms。`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const ratio = tLarge / Math.max(tSmall, 0.001);
|
|
67
|
+
assert.ok(ratio < ratioLimit, `${label}:输入 ${base}→${base * 2}(翻倍)耗时 ${tSmall.toFixed(2)}ms→`
|
|
68
|
+
+ `${tLarge.toFixed(2)}ms,增长 ${ratio.toFixed(1)}× —— 应 <${ratioLimit}×。\n`
|
|
69
|
+
+ '★接近 4× 意味着**二次回溯**:某条正则在每个起始位置都重新贪婪扫描再回退。\n'
|
|
70
|
+
+ ' 人类文档是攻击者可控输入,一份构造过的文档就能钉死抽取线程。');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 绝对预算判据——★用于「修复态极快、缺陷态极慢」的场景。
|
|
74
|
+
*
|
|
75
|
+
* <h2>为什么这三条不用增长率</h2>
|
|
76
|
+
*
|
|
77
|
+
* 增长率在这里有个**无解的两难**(2026-09-13 在 main 的 CI 上实证):
|
|
78
|
+
*
|
|
79
|
+
* - base 小 → `large` 落在 0.1〜2ms 的**噪声地板**,测的是调度器不是代码。
|
|
80
|
+
* CI 实测把 0.49ms→1.77ms 判成 3.6× 而变红,而同一份代码本机
|
|
81
|
+
* 7 次取最小是干净的 2.01×。
|
|
82
|
+
* - base 大 → 脱离噪声了,但**缺陷态会跑到几百秒**(无锚模式 ×4 增长,
|
|
83
|
+
* base=400000 时约 1028s/次)——门禁不是变红,而是**挂死**。
|
|
84
|
+
* 一个挂死的门禁比 flaky 的更糟:它连"哪里错了"都报不出来。
|
|
85
|
+
*
|
|
86
|
+
* <p>绝对预算同时解决两头:只需**一次**测量(不比值 ⇒ 不放大噪声),
|
|
87
|
+
* 且在缺陷态能**很快**超预算并报错。
|
|
88
|
+
*
|
|
89
|
+
* <p>实测 n=80000:修复态 ~2.5ms,缺陷态(撤掉左锚)约 10s ——
|
|
90
|
+
* **差 2500 倍**。取 500ms:修复态 125× 余量、缺陷态超 20 倍必红。
|
|
91
|
+
*/
|
|
92
|
+
function assertWithinBudget(label, input, fn, budgetMs = 500) {
|
|
93
|
+
const ms = timeOf(() => { fn(input); });
|
|
94
|
+
assert.ok(ms < budgetMs, `${label}:${input.length} 长度输入耗时 ${ms.toFixed(1)}ms —— 应 <${budgetMs}ms。\n`
|
|
95
|
+
+ '★修复态实测 ~2.5ms,缺陷态(撤掉 `(?<![\\d.])` 左锚)同规模约 10s(2500×)。\n'
|
|
96
|
+
+ ' 超预算说明 ReDoS 左锚被去掉了,长数字串上的全量回退回来了。');
|
|
97
|
+
}
|
|
98
|
+
describe('ReDoS 时间预算门禁 — 量增长率而非绝对耗时', () => {
|
|
99
|
+
it('★QuantityIR:长数字串上呈次二次增长', () => {
|
|
100
|
+
// ★改用绝对预算:见 assertWithinBudget 的注释(增长率在此有无解的两难)。
|
|
101
|
+
assertWithinBudget('extractQuantities / 纯数字串', '$' + '1'.repeat(80000), s => extractQuantities(s));
|
|
102
|
+
});
|
|
103
|
+
it('★QuantityIR:数字与小数点混排(最坏形态)也呈次二次增长', () => {
|
|
104
|
+
// ★比纯数字更毒:小数点让 `(?:\.\d+)?` 这个可选组也参与回溯。
|
|
105
|
+
assertWithinBudget('extractQuantities / 数字+小数点', '1.'.repeat(40000), s => extractQuantities(s));
|
|
106
|
+
});
|
|
107
|
+
it('★QuantityIR:贴着单位的长数字串(回溯最容易被触发的形态)', () => {
|
|
108
|
+
// 末尾放一个**能匹配上**的单位,让正则引擎有理由一路试下去。
|
|
109
|
+
// ★这条正是 2026-09-13 在 main 的 CI 上以 0.49ms→1.77ms 判 3.6× 变红的那条。
|
|
110
|
+
assertWithinBudget('extractQuantities / 长数字+单位', '1'.repeat(80000) + '%', s => extractQuantities(s));
|
|
111
|
+
});
|
|
112
|
+
it('★SourceIR:长文档解析呈次二次增长', () => {
|
|
113
|
+
// SourceIR 同样吃人类文档,同样是攻击面。
|
|
114
|
+
assertSubQuadratic('parseSourceIr / 多级标题文档', n => Array.from({ length: n }, (_, i) => `${'#'.repeat((i % 6) + 1)} 标题 ${i}\n正文 ${i}`).join('\n'), s => parseSourceIr(s),
|
|
115
|
+
// ★base 从 2000 提到 20000:原先 large 仅 1.36ms,在噪声地板附近判增长率
|
|
116
|
+
// 纯属测量噪声(实测 8 次里红 1 次,0.34ms→1.36ms 报 4.0×)。
|
|
117
|
+
// 提高规模让 large 稳定超过 10ms,比值才有意义。
|
|
118
|
+
// ★20000 仍不够(3.22ms→10.02ms 曾报 3.1×),再提到 40000。
|
|
119
|
+
40000);
|
|
120
|
+
});
|
|
121
|
+
it('★SourceIR:长单行(无换行)不得触发回溯', () => {
|
|
122
|
+
assertWithinBudget('parseSourceIr / 超长单行', '#'.repeat(80000) + ' 标题', s => parseSourceIr(s));
|
|
123
|
+
});
|
|
124
|
+
it('★SourceIR:**匹配失败**形态(无标题)—— 指数型回溯只活在这里', () => {
|
|
125
|
+
// ★这条补的是上一条的结构性盲区,由独立审查者发现。
|
|
126
|
+
//
|
|
127
|
+
// 上一条的载荷是 `'#'.repeat(n) + ' 标题'`——尾部的「 标题」让
|
|
128
|
+
// `/^(#{1,6})\s+\S/` **立刻匹配成功**,引擎根本不回溯。
|
|
129
|
+
// **ReDoS 活在「匹配失败」的形态里。**
|
|
130
|
+
//
|
|
131
|
+
// 实证(把 `#{1,6}` 换成嵌套量词 `(#+)+` 这种典型回归):
|
|
132
|
+
// 有标题(成功):16→0ms 20→0ms 24→0ms 28→0ms ← 完全看不见
|
|
133
|
+
// 无标题(失败):16→0.2ms 20→3.7ms 24→59ms 28→938ms ← 指数
|
|
134
|
+
//
|
|
135
|
+
// 即:上一条门禁**抓不到**把模式改成嵌套量词的回归,而那是 28 个字符
|
|
136
|
+
// 就能打死一个线程的指数级漏洞。
|
|
137
|
+
//
|
|
138
|
+
// ★另一个盲区:**尺度**。指数型在 n=20000 时不是「慢」而是「永不返回」,
|
|
139
|
+
// 用 base=20000 反而测不到。故这里用**小 n 的绝对上界**。
|
|
140
|
+
const probe = (n) => {
|
|
141
|
+
const s = '#'.repeat(n);
|
|
142
|
+
parseSourceIr(s);
|
|
143
|
+
const t = process.hrtime.bigint();
|
|
144
|
+
parseSourceIr(s);
|
|
145
|
+
return Number(process.hrtime.bigint() - t) / 1e6;
|
|
146
|
+
};
|
|
147
|
+
for (const n of [16, 20, 24, 28, 32]) {
|
|
148
|
+
const ms = probe(n);
|
|
149
|
+
assert.ok(ms < 20, `parseSourceIr('#'.repeat(${n}))(**无标题**,匹配失败形态)耗时 ${ms.toFixed(1)}ms —— 应 <20ms。\n`
|
|
150
|
+
+ '★这么小的输入却这么慢 = **指数级**回溯,典型成因是标题模式里出现了\n'
|
|
151
|
+
+ ' 嵌套量词(如 `(#+)+`)。注意:同样的缺陷在「有标题」的载荷上完全看不见。');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
it('★门禁自身可变红 —— 用已知二次算法验证量具连着被测对象', () => {
|
|
155
|
+
// ★本仓反复记录的教训:「门禁在结构上无法变红」是最危险的假绿。
|
|
156
|
+
// 这条把一个**已知二次**的函数喂给同一套判定逻辑,若判定通过,
|
|
157
|
+
// 说明 assertSubQuadratic 根本测不出二次增长——量具没连上。
|
|
158
|
+
const quadratic = (s) => {
|
|
159
|
+
let acc = 0;
|
|
160
|
+
for (let i = 0; i < s.length; i++)
|
|
161
|
+
for (let j = 0; j < s.length; j++)
|
|
162
|
+
acc += 1;
|
|
163
|
+
return acc;
|
|
164
|
+
};
|
|
165
|
+
assert.throws(() => { assertSubQuadratic('自检', n => 'x'.repeat(n), quadratic, 3000); }, /增长 .* —— 应 </, '已知二次算法未被判定为超线性 —— assertSubQuadratic 失效,本文件所有门禁都是假绿。');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=redos-budget.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redos-budget.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/redos-budget.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-ir.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/source-ir.test.ts"],"names":[],"mappings":""}
|