@aster-cloud/aster-lang-ts 1.0.28 → 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.
Files changed (207) hide show
  1. package/dist/scripts/generate_error_codes.js +49 -6
  2. package/dist/scripts/generate_error_codes.js.map +1 -1
  3. package/dist/src/ai/providers/openai.d.ts +10 -0
  4. package/dist/src/ai/providers/openai.d.ts.map +1 -1
  5. package/dist/src/ai/providers/openai.js +4 -0
  6. package/dist/src/ai/providers/openai.js.map +1 -1
  7. package/dist/src/config/lexicons/regex-guard.d.ts.map +1 -1
  8. package/dist/src/config/lexicons/regex-guard.js +128 -0
  9. package/dist/src/config/lexicons/regex-guard.js.map +1 -1
  10. package/dist/src/core/interpreter.d.ts.map +1 -1
  11. package/dist/src/core/interpreter.js +221 -27
  12. package/dist/src/core/interpreter.js.map +1 -1
  13. package/dist/src/cst/cst_builder.js +0 -14
  14. package/dist/src/cst/cst_builder.js.map +1 -1
  15. package/dist/src/cst/cst_printer.d.ts.map +1 -1
  16. package/dist/src/cst/cst_printer.js +20 -3
  17. package/dist/src/cst/cst_printer.js.map +1 -1
  18. package/dist/src/diagnostics/error_codes.d.ts +3 -3
  19. package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
  20. package/dist/src/diagnostics/error_codes.js +509 -78
  21. package/dist/src/diagnostics/error_codes.js.map +1 -1
  22. package/dist/src/formatter.d.ts +7 -0
  23. package/dist/src/formatter.d.ts.map +1 -1
  24. package/dist/src/formatter.js +175 -5
  25. package/dist/src/formatter.js.map +1 -1
  26. package/dist/src/frontend/canonicalizer.d.ts.map +1 -1
  27. package/dist/src/frontend/canonicalizer.js +47 -6
  28. package/dist/src/frontend/canonicalizer.js.map +1 -1
  29. package/dist/src/frontend/lexer.d.ts +2 -1
  30. package/dist/src/frontend/lexer.d.ts.map +1 -1
  31. package/dist/src/frontend/lexer.js +10 -3
  32. package/dist/src/frontend/lexer.js.map +1 -1
  33. package/dist/src/frontend/transformers.d.ts.map +1 -1
  34. package/dist/src/frontend/transformers.js +50 -3
  35. package/dist/src/frontend/transformers.js.map +1 -1
  36. package/dist/src/lower_to_core.js +101 -4
  37. package/dist/src/lower_to_core.js.map +1 -1
  38. package/dist/src/lsp/canonicalize-options.d.ts +26 -0
  39. package/dist/src/lsp/canonicalize-options.d.ts.map +1 -0
  40. package/dist/src/lsp/canonicalize-options.js +14 -0
  41. package/dist/src/lsp/canonicalize-options.js.map +1 -0
  42. package/dist/src/lsp/completion.d.ts.map +1 -1
  43. package/dist/src/lsp/completion.js +22 -4
  44. package/dist/src/lsp/completion.js.map +1 -1
  45. package/dist/src/lsp/server.js +29 -1
  46. package/dist/src/lsp/server.js.map +1 -1
  47. package/dist/src/lsp/tenant-init.d.ts +55 -0
  48. package/dist/src/lsp/tenant-init.d.ts.map +1 -0
  49. package/dist/src/lsp/tenant-init.js +88 -0
  50. package/dist/src/lsp/tenant-init.js.map +1 -0
  51. package/dist/src/mapping/candidate-generator.d.ts +84 -0
  52. package/dist/src/mapping/candidate-generator.d.ts.map +1 -0
  53. package/dist/src/mapping/candidate-generator.js +114 -0
  54. package/dist/src/mapping/candidate-generator.js.map +1 -0
  55. package/dist/src/mapping/entity-proposer.d.ts +85 -0
  56. package/dist/src/mapping/entity-proposer.d.ts.map +1 -0
  57. package/dist/src/mapping/entity-proposer.js +162 -0
  58. package/dist/src/mapping/entity-proposer.js.map +1 -0
  59. package/dist/src/mapping/mapping-ir.d.ts +85 -0
  60. package/dist/src/mapping/mapping-ir.d.ts.map +1 -0
  61. package/dist/src/mapping/mapping-ir.js +195 -0
  62. package/dist/src/mapping/mapping-ir.js.map +1 -0
  63. package/dist/src/mapping/proof-ir.d.ts +125 -0
  64. package/dist/src/mapping/proof-ir.d.ts.map +1 -0
  65. package/dist/src/mapping/proof-ir.js +48 -0
  66. package/dist/src/mapping/proof-ir.js.map +1 -0
  67. package/dist/src/mapping/quantity-ir.d.ts +93 -0
  68. package/dist/src/mapping/quantity-ir.d.ts.map +1 -0
  69. package/dist/src/mapping/quantity-ir.js +151 -0
  70. package/dist/src/mapping/quantity-ir.js.map +1 -0
  71. package/dist/src/mapping/source-ir.d.ts +82 -0
  72. package/dist/src/mapping/source-ir.d.ts.map +1 -0
  73. package/dist/src/mapping/source-ir.js +231 -0
  74. package/dist/src/mapping/source-ir.js.map +1 -0
  75. package/dist/src/nodeid/change-impact.d.ts +77 -0
  76. package/dist/src/nodeid/change-impact.d.ts.map +1 -0
  77. package/dist/src/nodeid/change-impact.js +132 -0
  78. package/dist/src/nodeid/change-impact.js.map +1 -0
  79. package/dist/src/nodeid/node-id-map.d.ts +69 -0
  80. package/dist/src/nodeid/node-id-map.d.ts.map +1 -0
  81. package/dist/src/nodeid/node-id-map.js +193 -0
  82. package/dist/src/nodeid/node-id-map.js.map +1 -0
  83. package/dist/src/parser/decl-parser.d.ts.map +1 -1
  84. package/dist/src/parser/decl-parser.js +10 -4
  85. package/dist/src/parser/decl-parser.js.map +1 -1
  86. package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
  87. package/dist/src/parser/expr-stmt-parser.js +36 -24
  88. package/dist/src/parser/expr-stmt-parser.js.map +1 -1
  89. package/dist/src/parser/span-utils.d.ts +25 -0
  90. package/dist/src/parser/span-utils.d.ts.map +1 -1
  91. package/dist/src/parser/span-utils.js +43 -0
  92. package/dist/src/parser/span-utils.js.map +1 -1
  93. package/dist/src/parser/type-parser.d.ts.map +1 -1
  94. package/dist/src/parser/type-parser.js +21 -0
  95. package/dist/src/parser/type-parser.js.map +1 -1
  96. package/dist/src/typecheck/expression.d.ts.map +1 -1
  97. package/dist/src/typecheck/expression.js +26 -0
  98. package/dist/src/typecheck/expression.js.map +1 -1
  99. package/dist/test/fuzz/fuzz.test.js +62 -2
  100. package/dist/test/fuzz/fuzz.test.js.map +1 -1
  101. package/dist/test/unit/call-arity.test.d.ts +2 -0
  102. package/dist/test/unit/call-arity.test.d.ts.map +1 -0
  103. package/dist/test/unit/call-arity.test.js +71 -0
  104. package/dist/test/unit/call-arity.test.js.map +1 -0
  105. package/dist/test/unit/canonicalizer/canonicalizer.test.js +42 -4
  106. package/dist/test/unit/canonicalizer/canonicalizer.test.js.map +1 -1
  107. package/dist/test/unit/canonicalizer/line-preservation.test.d.ts +2 -0
  108. package/dist/test/unit/canonicalizer/line-preservation.test.d.ts.map +1 -0
  109. package/dist/test/unit/canonicalizer/line-preservation.test.js +71 -0
  110. package/dist/test/unit/canonicalizer/line-preservation.test.js.map +1 -0
  111. package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts +2 -0
  112. package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts.map +1 -0
  113. package/dist/test/unit/canonicalizer/marker-restore-linear.test.js +98 -0
  114. package/dist/test/unit/canonicalizer/marker-restore-linear.test.js.map +1 -0
  115. package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts +2 -0
  116. package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts.map +1 -0
  117. package/dist/test/unit/canonicalizer/possessive-anchor.test.js +101 -0
  118. package/dist/test/unit/canonicalizer/possessive-anchor.test.js.map +1 -0
  119. package/dist/test/unit/formatter-redos.test.d.ts +2 -0
  120. package/dist/test/unit/formatter-redos.test.d.ts.map +1 -0
  121. package/dist/test/unit/formatter-redos.test.js +196 -0
  122. package/dist/test/unit/formatter-redos.test.js.map +1 -0
  123. package/dist/test/unit/interpreter/interpreter.test.js +7 -2
  124. package/dist/test/unit/interpreter/interpreter.test.js.map +1 -1
  125. package/dist/test/unit/lexer/bom.test.d.ts +2 -0
  126. package/dist/test/unit/lexer/bom.test.d.ts.map +1 -0
  127. package/dist/test/unit/lexer/bom.test.js +49 -0
  128. package/dist/test/unit/lexer/bom.test.js.map +1 -0
  129. package/dist/test/unit/list-get-bounds.test.d.ts +2 -0
  130. package/dist/test/unit/list-get-bounds.test.d.ts.map +1 -0
  131. package/dist/test/unit/list-get-bounds.test.js +66 -0
  132. package/dist/test/unit/list-get-bounds.test.js.map +1 -0
  133. package/dist/test/unit/long-bigint-exact.test.d.ts +2 -0
  134. package/dist/test/unit/long-bigint-exact.test.d.ts.map +1 -0
  135. package/dist/test/unit/long-bigint-exact.test.js +79 -0
  136. package/dist/test/unit/long-bigint-exact.test.js.map +1 -0
  137. package/dist/test/unit/lsp/completion-lexicon.test.d.ts +2 -0
  138. package/dist/test/unit/lsp/completion-lexicon.test.d.ts.map +1 -0
  139. package/dist/test/unit/lsp/completion-lexicon.test.js +83 -0
  140. package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -0
  141. package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts +2 -0
  142. package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts.map +1 -0
  143. package/dist/test/unit/lsp/tenant-vocabulary.test.js +187 -0
  144. package/dist/test/unit/lsp/tenant-vocabulary.test.js.map +1 -0
  145. package/dist/test/unit/map-builtins.test.js +163 -3
  146. package/dist/test/unit/map-builtins.test.js.map +1 -1
  147. package/dist/test/unit/map-structural-equality.test.d.ts +2 -0
  148. package/dist/test/unit/map-structural-equality.test.d.ts.map +1 -0
  149. package/dist/test/unit/map-structural-equality.test.js +64 -0
  150. package/dist/test/unit/map-structural-equality.test.js.map +1 -0
  151. package/dist/test/unit/map-type-guard.test.js +8 -5
  152. package/dist/test/unit/map-type-guard.test.js.map +1 -1
  153. package/dist/test/unit/mapping/candidate-generator.test.d.ts +2 -0
  154. package/dist/test/unit/mapping/candidate-generator.test.d.ts.map +1 -0
  155. package/dist/test/unit/mapping/candidate-generator.test.js +177 -0
  156. package/dist/test/unit/mapping/candidate-generator.test.js.map +1 -0
  157. package/dist/test/unit/mapping/entity-proposer.test.d.ts +2 -0
  158. package/dist/test/unit/mapping/entity-proposer.test.d.ts.map +1 -0
  159. package/dist/test/unit/mapping/entity-proposer.test.js +168 -0
  160. package/dist/test/unit/mapping/entity-proposer.test.js.map +1 -0
  161. package/dist/test/unit/mapping/mapping-ir.test.d.ts +2 -0
  162. package/dist/test/unit/mapping/mapping-ir.test.d.ts.map +1 -0
  163. package/dist/test/unit/mapping/mapping-ir.test.js +144 -0
  164. package/dist/test/unit/mapping/mapping-ir.test.js.map +1 -0
  165. package/dist/test/unit/mapping/proof-ir.test.d.ts +2 -0
  166. package/dist/test/unit/mapping/proof-ir.test.d.ts.map +1 -0
  167. package/dist/test/unit/mapping/proof-ir.test.js +119 -0
  168. package/dist/test/unit/mapping/proof-ir.test.js.map +1 -0
  169. package/dist/test/unit/mapping/quantity-ir.test.d.ts +2 -0
  170. package/dist/test/unit/mapping/quantity-ir.test.d.ts.map +1 -0
  171. package/dist/test/unit/mapping/quantity-ir.test.js +144 -0
  172. package/dist/test/unit/mapping/quantity-ir.test.js.map +1 -0
  173. package/dist/test/unit/mapping/quantity-redos-property.test.d.ts +2 -0
  174. package/dist/test/unit/mapping/quantity-redos-property.test.d.ts.map +1 -0
  175. package/dist/test/unit/mapping/quantity-redos-property.test.js +182 -0
  176. package/dist/test/unit/mapping/quantity-redos-property.test.js.map +1 -0
  177. package/dist/test/unit/mapping/redos-budget.test.d.ts +2 -0
  178. package/dist/test/unit/mapping/redos-budget.test.d.ts.map +1 -0
  179. package/dist/test/unit/mapping/redos-budget.test.js +168 -0
  180. package/dist/test/unit/mapping/redos-budget.test.js.map +1 -0
  181. package/dist/test/unit/mapping/source-ir.test.d.ts +2 -0
  182. package/dist/test/unit/mapping/source-ir.test.d.ts.map +1 -0
  183. package/dist/test/unit/mapping/source-ir.test.js +113 -0
  184. package/dist/test/unit/mapping/source-ir.test.js.map +1 -0
  185. package/dist/test/unit/nodeid/change-impact.test.d.ts +2 -0
  186. package/dist/test/unit/nodeid/change-impact.test.d.ts.map +1 -0
  187. package/dist/test/unit/nodeid/change-impact.test.js +152 -0
  188. package/dist/test/unit/nodeid/change-impact.test.js.map +1 -0
  189. package/dist/test/unit/nodeid/rename-declaration.test.d.ts +2 -0
  190. package/dist/test/unit/nodeid/rename-declaration.test.d.ts.map +1 -0
  191. package/dist/test/unit/nodeid/rename-declaration.test.js +75 -0
  192. package/dist/test/unit/nodeid/rename-declaration.test.js.map +1 -0
  193. package/dist/test/unit/parse-type-recursion.test.d.ts +2 -0
  194. package/dist/test/unit/parse-type-recursion.test.d.ts.map +1 -0
  195. package/dist/test/unit/parse-type-recursion.test.js +58 -0
  196. package/dist/test/unit/parse-type-recursion.test.js.map +1 -0
  197. package/dist/test/unit/regex-guard.test.js +64 -0
  198. package/dist/test/unit/regex-guard.test.js.map +1 -1
  199. package/dist/test/unit/strict-int-coercion.test.d.ts +2 -0
  200. package/dist/test/unit/strict-int-coercion.test.d.ts.map +1 -0
  201. package/dist/test/unit/strict-int-coercion.test.js +96 -0
  202. package/dist/test/unit/strict-int-coercion.test.js.map +1 -0
  203. package/dist/test/unit/ufcs-receiver.test.d.ts +2 -0
  204. package/dist/test/unit/ufcs-receiver.test.d.ts.map +1 -0
  205. package/dist/test/unit/ufcs-receiver.test.js +75 -0
  206. package/dist/test/unit/ufcs-receiver.test.js.map +1 -0
  207. package/package.json +1 -1
@@ -0,0 +1,168 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseProposals, proposeEntities, verifyEntityCandidate } from '../../../src/mapping/entity-proposer.js';
4
+ import { parseSourceIr } from '../../../src/mapping/source-ir.js';
5
+ import { extractQuantities } from '../../../src/mapping/quantity-ir.js';
6
+ /**
7
+ * Entity 提出器(ADR 0037 §3/§11.5)—— 三段式的第①段。
8
+ *
9
+ * <p>核心契约:**LLM 只能提出候选,不能定义什么叫正确**。
10
+ * 本测试用假 provider 驱动,不打真实 LLM——判定逻辑必须可离线验证。
11
+ */
12
+ const DOC = [
13
+ '# 付款审批政策', '',
14
+ '单笔付款超过 $10,000 时需要财务经理审批。',
15
+ '低于该额度由部门主管批准。', '',
16
+ ].join('\n');
17
+ const BY = 'llm:fake/test-model';
18
+ /** 假 provider:返回预置文本,记录收到的请求。 */
19
+ function fakeProvider(reply) {
20
+ // ★用 `| undefined` 而非 `?`:tsconfig 开了 exactOptionalPropertyTypes,
21
+ // 「属性可为 undefined」与「属性可缺席」是两回事。
22
+ const p = {
23
+ lastRequest: undefined,
24
+ async generate(request) {
25
+ p.lastRequest = request;
26
+ return {
27
+ content: reply, model: 'test-model',
28
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
29
+ };
30
+ },
31
+ getName: () => 'fake',
32
+ getModel: () => 'test-model',
33
+ };
34
+ return p;
35
+ }
36
+ /** 造一条「位置与文本都正确」的提议。 */
37
+ function truthful(text) {
38
+ return JSON.stringify([{ text, kind: 'Role', start: DOC.indexOf(text) }]);
39
+ }
40
+ describe('Entity 提出器 — 三段式第①段', () => {
41
+ it('★位置与文本都对的候选被接受', () => {
42
+ const r = parseProposals(truthful('财务经理'), DOC, 0, BY);
43
+ assert.strictEqual(r.candidates.length, 1, `应接受 1 条,实际 ${JSON.stringify(r)}`);
44
+ assert.strictEqual(r.candidates[0].text, '财务经理');
45
+ assert.strictEqual(r.candidates[0].proposedKind, 'Role');
46
+ assert.strictEqual(r.candidates[0].proposedBy, BY);
47
+ assert.strictEqual(DOC.slice(r.candidates[0].span.start, r.candidates[0].span.end), '财务经理');
48
+ });
49
+ it('★幻觉闸门:文本不在声称位置上 → 丢弃并报告', () => {
50
+ // LLM 最常见的幻觉:文本对但位置错。这会让双向导航指向错误的地方。
51
+ const wrongPos = JSON.stringify([{ text: '财务经理', kind: 'Role', start: 0 }]);
52
+ const r = parseProposals(wrongPos, DOC, 0, BY);
53
+ assert.strictEqual(r.candidates.length, 0, '位置错的候选必须丢弃。');
54
+ assert.strictEqual(r.rejected.length, 1);
55
+ assert.match(r.rejected[0].why, /不符/, `应说明不符,实际:${r.rejected[0].why}`);
56
+ });
57
+ it('★幻觉闸门:文档里根本没有的文本 → 丢弃', () => {
58
+ // 凭空编造的实体。
59
+ const invented = JSON.stringify([{ text: '首席合规官', kind: 'Role', start: 20 }]);
60
+ const r = parseProposals(invented, DOC, 0, BY);
61
+ assert.strictEqual(r.candidates.length, 0, '文档里不存在的文本必须丢弃。');
62
+ assert.strictEqual(r.rejected.length, 1);
63
+ });
64
+ it('★被丢弃的条目必须如实报告,不得静默吞掉', () => {
65
+ // 反向守卫:若 rejected 恒为空,「LLM 产出了不合规内容」这件事就消失了,
66
+ // 而那恰恰是评估模型可靠性最重要的信号。
67
+ const mixed = JSON.stringify([
68
+ { text: '财务经理', kind: 'Role', start: DOC.indexOf('财务经理') }, // 对
69
+ { text: '不存在的角色', kind: 'Role', start: 5 }, // 错
70
+ { text: '部门主管', kind: 'Party', start: 999 }, // 越界
71
+ { notText: 1 }, // 格式错
72
+ ]);
73
+ const r = parseProposals(mixed, DOC, 0, BY);
74
+ assert.strictEqual(r.candidates.length, 1, '只应接受那条正确的。');
75
+ assert.strictEqual(r.rejected.length, 3, `三条不合规都要报告,实际 ${JSON.stringify(r.rejected)}`);
76
+ assert.ok(r.rejected.every(x => x.why.length > 0), '每条丢弃都要说明原因。');
77
+ });
78
+ it('非 JSON / 非数组输出整体丢弃,不崩', () => {
79
+ for (const bad of ['我找到了这些实体:财务经理', '{"text":"x"}', '']) {
80
+ const r = parseProposals(bad, DOC, 0, BY);
81
+ assert.strictEqual(r.candidates.length, 0);
82
+ assert.strictEqual(r.rejected.length, 1, `输入 ${JSON.stringify(bad)} 应报告 1 条丢弃`);
83
+ }
84
+ });
85
+ it('容忍 LLM 套的 ```json 围栏(prompt 已要求不要,但模型常忘)', () => {
86
+ const fenced = '```json\n' + truthful('部门主管') + '\n```';
87
+ assert.strictEqual(parseProposals(fenced, DOC, 0, BY).candidates.length, 1);
88
+ });
89
+ it('★scope 偏移必须平移回原文(否则候选整体错位)', () => {
90
+ // LLM 看到的是切片,它给的 start 是切片内偏移;必须加上 scope 起点。
91
+ const base = 10;
92
+ const sliced = DOC.slice(base);
93
+ const inSlice = sliced.indexOf('财务经理');
94
+ const r = parseProposals(JSON.stringify([{ text: '财务经理', kind: 'Role', start: inSlice }]), DOC, base, BY);
95
+ assert.strictEqual(r.candidates.length, 1, `切片内偏移未被平移回原文 —— 实际 ${JSON.stringify(r.rejected)}`);
96
+ assert.strictEqual(r.candidates[0].span.start, base + inSlice);
97
+ });
98
+ it('★第②段恒判 REVIEW_REQUIRED —— 机器不得替人下结论', () => {
99
+ // ADR §3:AI 可以提出映射,但不能定义什么叫正确。
100
+ // Entity 的目标是**语义**对应,而 verifier 只能读 kind/value/name/origin
101
+ // (类型层是两引擎合法分叉的层,不在可依赖范围内,见 §5.1)。
102
+ const r = parseProposals(truthful('财务经理'), DOC, 0, BY);
103
+ const verdict = verifyEntityCandidate(r.candidates[0]);
104
+ assert.strictEqual(verdict.verdict, 'REVIEW_REQUIRED');
105
+ assert.match(verdict.reason, /语义/, '判定理由应说明这是语义判断。');
106
+ });
107
+ it('★temperature 必须为 0:同一文档反复提出应可复现', () => {
108
+ // 候选生成的可复现性是审计的前提——否则「上次为什么提了这条」永远说不清。
109
+ const p = fakeProvider('[]');
110
+ return proposeEntities(DOC, p).then(() => {
111
+ assert.strictEqual(p.lastRequest?.temperature, 0, `temperature 应为 0,实际 ${p.lastRequest?.temperature}`);
112
+ });
113
+ });
114
+ it('★scope 限定时只把该节点的文本喂给 LLM', () => {
115
+ const ir = parseSourceIr(DOC);
116
+ const heading = ir.children[0];
117
+ const p = fakeProvider('[]');
118
+ return proposeEntities(DOC, p, heading).then(() => {
119
+ assert.strictEqual(p.lastRequest?.prompt, DOC.slice(heading.span.start, heading.span.end), 'scope 限定时应只喂该节点的文本,而不是整篇文档。');
120
+ });
121
+ });
122
+ it('★与 Quantity 分工不重叠:Entity 不碰金额', () => {
123
+ // Quantity 由确定性模块处理(ADR §11);两边都抽会产生重复候选。
124
+ const moneyTexts = extractQuantities(DOC).map(q => q.text);
125
+ assert.ok(moneyTexts.includes('$10,000'), '前置:文档里应有金额。');
126
+ // 即使 LLM 错误地提出了金额,它也只是一条普通候选——不会被特殊对待,
127
+ // 但契约上 systemPrompt 已明确要求不要输出。此处钉住 prompt 里那条要求。
128
+ const p = fakeProvider('[]');
129
+ return proposeEntities(DOC, p).then(() => {
130
+ assert.match(p.lastRequest?.systemPrompt ?? '', /不要输出金额/, 'systemPrompt 必须明确要求 LLM 不输出金额等数量 —— 那是确定性模块的职责。');
131
+ });
132
+ });
133
+ it('★注入守卫:kind 含标签字符 → 丢弃(防 reason 被渲染成 HTML)', () => {
134
+ // kind 是 **LLM 完全可控**的自由字符串,且原样进入 reason(人类可读文本)。
135
+ // 本仓有 6 处 dangerouslySetInnerHTML——一旦有人把 reason 接进去就是存储型 XSS。
136
+ // ★不能在此做 HTML 转义:那会改变 kind 的值,违反 §12.4「原样保留」。
137
+ // 正确做法是在源头限制**形态**——合法类别名本就不含这些字符。
138
+ const payload = JSON.stringify([
139
+ { text: '财务经理', kind: '<img src=x onerror=alert(1)>', start: DOC.indexOf('财务经理') },
140
+ ]);
141
+ const r = parseProposals(payload, DOC, 0, BY);
142
+ assert.strictEqual(r.candidates.length, 0, '含标签字符的 kind 必须丢弃。');
143
+ assert.match(r.rejected[0].why, /标签或控制字符/);
144
+ });
145
+ it('★合法类别(中英文)不得被注入守卫误伤', () => {
146
+ // 反向守卫:若字符白名单收得过紧,正常类别会被拒——那比不设防更糟,
147
+ // 因为它会静默丢掉真实候选。
148
+ for (const kind of ['Role', 'Obligation', '角色', '义务主体', 'Party_A']) {
149
+ const r = parseProposals(JSON.stringify([{ text: '财务经理', kind, start: DOC.indexOf('财务经理') }]), DOC, 0, BY);
150
+ assert.strictEqual(r.candidates.length, 1, `合法类别 ${JSON.stringify(kind)} 被误拒:${JSON.stringify(r.rejected)}`);
151
+ }
152
+ });
153
+ it('★资源上限:条目数超限整批拒绝,且如实报告', () => {
154
+ // 防「LLM 返回巨量条目」耗尽下游内存。★不静默截断——
155
+ // 调用方必须知道「LLM 返回量异常」这件事。
156
+ const many = JSON.stringify(Array.from({ length: 5000 }, () => ({ text: '财务经理', kind: 'Role', start: DOC.indexOf('财务经理') })));
157
+ const r = parseProposals(many, DOC, 0, BY);
158
+ assert.strictEqual(r.candidates.length, 0, '超限应整批拒绝。');
159
+ assert.strictEqual(r.rejected.length, 1);
160
+ assert.match(r.rejected[0].why, /条目数超上限/);
161
+ });
162
+ it('★超长 kind → 丢弃(防 reason 变成一大段不可控内容)', () => {
163
+ const r = parseProposals(JSON.stringify([{ text: '财务经理', kind: 'x'.repeat(200), start: DOC.indexOf('财务经理') }]), DOC, 0, BY);
164
+ assert.strictEqual(r.candidates.length, 0);
165
+ assert.match(r.rejected[0].why, /过长/);
166
+ });
167
+ });
168
+ //# sourceMappingURL=entity-proposer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-proposer.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/entity-proposer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mapping-ir.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping-ir.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/mapping-ir.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,144 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { canonicalize } from '../../../src/frontend/canonicalizer.js';
4
+ import { lex } from '../../../src/frontend/lexer.js';
5
+ import { parse } from '../../../src/parser.js';
6
+ import { lowerModule } from '../../../src/lower_to_core.js';
7
+ import { computeNodeIds } from '../../../src/nodeid/node-id-map.js';
8
+ import { verifyMapping } from '../../../src/mapping/mapping-ir.js';
9
+ /**
10
+ * MappingIR verifier(ADR 0037 §4/§7)。
11
+ *
12
+ * <p>本测试钉的是**契约**:哪些映射机器能证明、哪些必须交人、哪些能证伪。
13
+ * 不钉实现细节。
14
+ */
15
+ /** 从真实源码建「nodeId → 节点」解析器,走完整编译链。 */
16
+ function resolverOf(src) {
17
+ const ir = JSON.parse(JSON.stringify(lowerModule(parse(lex(canonicalize(src))).ast)));
18
+ const ids = computeNodeIds(ir);
19
+ // nodeId → 原始节点:按路径在 IR 上走一遍(nodeId 就是路径)
20
+ const nodeAt = (id) => {
21
+ let cur = ir;
22
+ for (const seg of id.slice(2).split('.')) {
23
+ if (seg === '')
24
+ continue;
25
+ const m = /^([A-Za-z0-9_]+)(?:\{(.*)\}|\[(\d+)\])?$/.exec(seg);
26
+ if (!m || cur === null || typeof cur !== 'object')
27
+ return undefined;
28
+ cur = cur[m[1]];
29
+ if (m[2] !== undefined) {
30
+ cur = cur.find(e => e?.name === m[2] || e?.path === m[2]);
31
+ }
32
+ else if (m[3] !== undefined) {
33
+ cur = cur[Number(m[3])];
34
+ }
35
+ }
36
+ return cur;
37
+ };
38
+ return {
39
+ resolve: (id) => {
40
+ const n = nodeAt(id);
41
+ if (!n || typeof n.kind !== 'string')
42
+ return undefined;
43
+ // ★不能写 `name: n.name as string | undefined`:tsconfig 开了
44
+ // exactOptionalPropertyTypes,显式 undefined 与「属性缺席」是两回事。
45
+ const out = { kind: n.kind, ...(n.value === undefined ? {} : { value: n.value }) };
46
+ return typeof n.name === 'string' ? { ...out, name: n.name } : out;
47
+ },
48
+ find: (kind, value) => {
49
+ for (const [id] of ids) {
50
+ const n = nodeAt(id);
51
+ if (n?.kind === kind && (value === undefined || n.value === value))
52
+ return id;
53
+ }
54
+ throw new Error(`语料里找不到 kind=${kind} value=${String(value)} 的节点`);
55
+ },
56
+ };
57
+ }
58
+ const POLICY = [
59
+ 'Module demo.approve.', '',
60
+ 'Rule approve given amount, produce:',
61
+ ' If amount greater than 10000:',
62
+ ' Return "REFER".',
63
+ ' Otherwise:',
64
+ ' Return "APPROVE".', '',
65
+ ].join('\n');
66
+ const mapping = (text, nodeId, start = 0) => ({ span: { start, end: start + text.length }, text, nodeId });
67
+ describe('MappingIR verifier — 机器能证明的那一类', () => {
68
+ it('★`"$10,000"` ↔ Int(10000):跨越「人类书写 ↔ IR 值」的鸿沟', () => {
69
+ // ADR §4 的原始场景。货币符号与千分位是人类惯例,IR 里只有 10000。
70
+ const { resolve, find } = resolverOf(POLICY);
71
+ const r = verifyMapping(mapping('$10,000', find('Int', 10000)), resolve);
72
+ assert.strictEqual(r.verdict, 'VERIFIED', `应判 VERIFIED(这正是 MappingIR 的价值所在)。实际 ${r.verdict}:${r.reason}`);
73
+ });
74
+ it('字符串字面量按原值比较', () => {
75
+ const { resolve, find } = resolverOf(POLICY);
76
+ const r = verifyMapping(mapping('"REFER"', find('String', 'REFER')), resolve);
77
+ assert.strictEqual(r.verdict, 'VERIFIED', r.reason);
78
+ });
79
+ it('★值矛盾必须 REJECTED,不得放过', () => {
80
+ // 反向守卫:没有这条,verifier 可以退化成「一律 VERIFIED」,
81
+ // 上面两条照样绿,而它就彻底没用了。
82
+ const { resolve, find } = resolverOf(POLICY);
83
+ const r = verifyMapping(mapping('$20,000', find('Int', 10000)), resolve);
84
+ assert.strictEqual(r.verdict, 'REJECTED', `文本 20000 与节点值 10000 矛盾,必须证伪。实际 ${r.verdict}`);
85
+ assert.match(r.reason, /矛盾/);
86
+ });
87
+ it('★带业务含义的映射一律交人,不猜(ADR §3)', () => {
88
+ // `"requires approval"` ↔ 某个 If/Call 节点:机器证不了。
89
+ // 判 REVIEW_REQUIRED 而**不是** REJECTED —— 那不是「错」,是「机器管不了」。
90
+ const { resolve, find } = resolverOf(POLICY);
91
+ const r = verifyMapping(mapping('requires approval', find('If')), resolve);
92
+ assert.strictEqual(r.verdict, 'REVIEW_REQUIRED', `非精确值节点应交人复核,实际 ${r.verdict}:${r.reason}`);
93
+ });
94
+ it('★Double 不参与机械验证(源码文本与 IR 值不可逆)', () => {
95
+ // 实测:`1.0` → number 1 → 回写 "1";`1e3` → 1000。文本不可还原,
96
+ // 机器无法证明「这段文本就是这个 Double」。故交人,而不是用近似规则假装能证。
97
+ const r = verifyMapping(mapping('1.0', '$.fake'), () => ({ kind: 'Double', value: 1 }));
98
+ assert.strictEqual(r.verdict, 'REVIEW_REQUIRED', `Double 应交人复核(不可逆),实际 ${r.verdict}:${r.reason}`);
99
+ // ★必须断言**判定依据**,不只是判定结果。
100
+ // 变异验证发现:把 Double 加进 EXACT_VALUE_KINDS 后,结论仍是
101
+ // REVIEW_REQUIRED —— 因为 parseLiteralFromText 里也没有 Double 分支,
102
+ // 两层防御恰好重叠。只断言 verdict 的话,EXACT_VALUE_KINDS 这道门
103
+ // **结构上无法变红**(假门禁)。
104
+ // 断言 reason 提到「不属于可机械证明的精确值字面量」才真正钉住那道门。
105
+ assert.match(r.reason, /不属于可机械证明的精确值字面量/, `应因「kind 不在可验证清单」而交人,而不是因为「解析不出值」。实际:${r.reason}`);
106
+ });
107
+ it('★canonical 形态 ≠ 源码文本:`$10,000.00` 必须能验过 Decimal("10000")', () => {
108
+ // 100.00m 在 IR 里是 value:"100"(尾随零被规范化)。
109
+ // 若做字符串相等比较,这条会误判 REJECTED —— 一个数值上完全正确的映射。
110
+ const r = verifyMapping(mapping('$10,000.00', '$.fake'), () => ({ kind: 'Decimal', value: '10000' }));
111
+ assert.strictEqual(r.verdict, 'VERIFIED', `尾随零差异不应导致失败(必须按值比较而非按文本)。实际 ${r.verdict}:${r.reason}`);
112
+ });
113
+ it('★超安全整数的 Long 不得丢精度', () => {
114
+ // 9007199254740993 一旦过一次 JS number 就变 …992(本仓实测踩过)。
115
+ // verifier 全程字符串比较,必须验得过。
116
+ const big = '9007199254740993';
117
+ const ok = verifyMapping(mapping(big, '$.fake'), () => ({ kind: 'Long', value: big }));
118
+ assert.strictEqual(ok.verdict, 'VERIFIED', ok.reason);
119
+ // 且**差 1** 必须能被识别出来——若中途过了 number,这两个值会变得相等。
120
+ const bad = verifyMapping(mapping('9007199254740992', '$.fake'), () => ({ kind: 'Long', value: big }));
121
+ assert.strictEqual(bad.verdict, 'REJECTED', '相差 1 的大整数必须证伪 —— 若判 VERIFIED,说明中途经过了 JS number 并丢了精度。');
122
+ });
123
+ it('节点不存在 → REJECTED(不能静默当成「待复核」)', () => {
124
+ const { resolve } = resolverOf(POLICY);
125
+ const r = verifyMapping(mapping('$10,000', '$.decls{nonexistent}'), resolve);
126
+ assert.strictEqual(r.verdict, 'REJECTED');
127
+ });
128
+ it('★span 与 text 必须自洽,否则双向导航会跳错位置', () => {
129
+ const { resolve, find } = resolverOf(POLICY);
130
+ const id = find('Int', 10000);
131
+ // 区间宽度 3,但 text 长 7 —— span 指向的根本不是这段文本
132
+ const inconsistent = verifyMapping({ span: { start: 0, end: 3 }, text: '$10,000', nodeId: id }, resolve);
133
+ assert.strictEqual(inconsistent.verdict, 'REJECTED', inconsistent.reason);
134
+ // 非法区间
135
+ const bad = verifyMapping({ span: { start: 5, end: 5 }, text: '', nodeId: id }, resolve);
136
+ assert.strictEqual(bad.verdict, 'REJECTED', bad.reason);
137
+ });
138
+ it('每条结论都必须给出依据(含 VERIFIED,便于审计复核)', () => {
139
+ const { resolve, find } = resolverOf(POLICY);
140
+ const r = verifyMapping(mapping('$10,000', find('Int', 10000)), resolve);
141
+ assert.ok(r.reason.length > 0, 'VERIFIED 也必须说明依据,否则审计时无法复核。');
142
+ });
143
+ });
144
+ //# sourceMappingURL=mapping-ir.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping-ir.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/mapping-ir.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=proof-ir.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof-ir.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/proof-ir.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,119 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { canonicalize } from '../../../src/frontend/canonicalizer.js';
4
+ import { lex } from '../../../src/frontend/lexer.js';
5
+ import { parse } from '../../../src/parser.js';
6
+ import { lowerModule } from '../../../src/lower_to_core.js';
7
+ import { computeNodeIds } from '../../../src/nodeid/node-id-map.js';
8
+ import { isApplicableTo, resolveEffective } from '../../../src/mapping/proof-ir.js';
9
+ /**
10
+ * ProofIR(ADR 0037 §2/§6.1)。
11
+ *
12
+ * <p>核心契约:**proof 记录不可变,有效性算出来**。
13
+ * 用真实 IR 驱动——proof 锚定 nodeId + contentHash,改源码后自动失效。
14
+ */
15
+ const V1 = [
16
+ 'Module demo.approve.', '',
17
+ 'Rule approve given amount, produce:',
18
+ ' If amount greater than 10000:',
19
+ ' Return "REFER".',
20
+ ' Otherwise:',
21
+ ' Return "APPROVE".', '',
22
+ ].join('\n');
23
+ const idsOf = (src) => computeNodeIds(JSON.parse(JSON.stringify(lowerModule(parse(lex(canonicalize(src))).ast))));
24
+ /** 找到阈值字面量节点(`10000`)。 */
25
+ function thresholdNode(src) {
26
+ for (const [id, v] of idsOf(src)) {
27
+ if (v.kind === 'Int')
28
+ return { nodeId: id, contentHash: v.contentHash };
29
+ }
30
+ throw new Error('找不到 Int 节点');
31
+ }
32
+ function proofOf(anchor, overrides = {}) {
33
+ return {
34
+ mapping: { span: { start: 0, end: 7 }, text: '$10,000', nodeId: anchor.nodeId },
35
+ verdict: 'VERIFIED',
36
+ subject: { kind: 'VERIFIER', by: 'mapping-ir/v1' },
37
+ rule: { id: 'exact-value', version: '1' },
38
+ verifiedAgainst: anchor,
39
+ reason: '文本解析为 Int 10000,与目标节点一致。',
40
+ recordedAt: '2026-09-13T00:00:00Z',
41
+ ...overrides,
42
+ };
43
+ }
44
+ const hashLookup = (src) => (id) => idsOf(src).get(id)?.contentHash;
45
+ describe('ProofIR — 不可变记录 + 有效性算出来', () => {
46
+ it('★内容未变 → proof 仍适用', () => {
47
+ const anchor = thresholdNode(V1);
48
+ const r = isApplicableTo(proofOf(anchor), hashLookup(V1));
49
+ assert.deepStrictEqual(r, { applicable: true });
50
+ });
51
+ it('★改了阈值 → proof 自动失效(记录本身不被修改)', () => {
52
+ const anchor = thresholdNode(V1);
53
+ const proof = proofOf(anchor);
54
+ const frozen = JSON.stringify(proof);
55
+ const v2 = V1.replace('10000', '20000');
56
+ assert.notStrictEqual(v2, V1, '★replace 未生效则本用例无效。');
57
+ const r = isApplicableTo(proof, hashLookup(v2));
58
+ assert.strictEqual(r.applicable, false, '阈值已变,proof 不应再适用。');
59
+ assert.strictEqual(r.applicable === false ? r.why : '', 'CONTENT_CHANGED');
60
+ // ★记录本身一个字节都没被改 —— 这是「不可变 + 水位线」的关键。
61
+ assert.strictEqual(JSON.stringify(proof), frozen, 'proof 记录被修改了 —— 应当只算有效性,绝不原地改写历史事实。');
62
+ });
63
+ it('节点已不存在 → NODE_GONE(与「内容变了」区分开)', () => {
64
+ const anchor = thresholdNode(V1);
65
+ const r = isApplicableTo(proofOf(anchor), () => undefined);
66
+ assert.strictEqual(r.applicable, false);
67
+ assert.strictEqual(r.applicable === false ? r.why : '', 'NODE_GONE');
68
+ });
69
+ it('★仲裁:只在「仍适用」的里面取最新', () => {
70
+ const anchor = thresholdNode(V1);
71
+ const old = proofOf(anchor, { recordedAt: '2026-01-01T00:00:00Z', verdict: 'REVIEW_REQUIRED' });
72
+ const recent = proofOf(anchor, { recordedAt: '2026-09-13T00:00:00Z', verdict: 'VERIFIED' });
73
+ const { effective } = resolveEffective([old, recent], hashLookup(V1));
74
+ assert.strictEqual(effective?.recordedAt, '2026-09-13T00:00:00Z', '应取最新的一条。');
75
+ });
76
+ it('★失效的 proof 不得参与仲裁(否则会拿旧版本的结论当现在的答案)', () => {
77
+ // 反向守卫:没有这条,resolveEffective 可以退化成「无脑取最新」——
78
+ // 上面那条照样绿,而一条早已失效的 proof 会被当成当前有效结论。
79
+ const anchor = thresholdNode(V1);
80
+ const stale = proofOf(anchor, { recordedAt: '2099-01-01T00:00:00Z' }); // 时间最新但内容已变
81
+ const v2 = V1.replace('10000', '20000');
82
+ const { effective } = resolveEffective([stale], hashLookup(v2));
83
+ assert.strictEqual(effective, undefined, '唯一的 proof 已失效,不应选出任何有效结论。');
84
+ });
85
+ it('★并存的分歧必须如实暴露,不静默吞掉', () => {
86
+ const anchor = thresholdNode(V1);
87
+ const machine = proofOf(anchor, {
88
+ recordedAt: '2026-09-13T00:00:00Z', verdict: 'REJECTED',
89
+ subject: { kind: 'VERIFIER', by: 'mapping-ir/v1' },
90
+ });
91
+ const expert = proofOf(anchor, {
92
+ recordedAt: '2026-09-12T00:00:00Z', verdict: 'VERIFIED',
93
+ subject: { kind: 'DOMAIN_EXPERT', by: 'alice' },
94
+ });
95
+ const { effective, conflicts } = resolveEffective([machine, expert], hashLookup(V1));
96
+ assert.strictEqual(effective?.verdict, 'REJECTED', '按时间应取机器那条。');
97
+ assert.strictEqual(conflicts.length, 1, '专家的相反结论必须出现在 conflicts 里 —— 静默吞掉会让「机器与专家分歧」这件事消失。');
98
+ assert.strictEqual(conflicts[0]?.subject.kind, 'DOMAIN_EXPERT');
99
+ });
100
+ it('结论相同的多条 proof 不算冲突', () => {
101
+ const anchor = thresholdNode(V1);
102
+ const a = proofOf(anchor, { recordedAt: '2026-09-13T00:00:00Z' });
103
+ const b = proofOf(anchor, { recordedAt: '2026-09-12T00:00:00Z' });
104
+ const { conflicts } = resolveEffective([a, b], hashLookup(V1));
105
+ assert.strictEqual(conflicts.length, 0, '同结论不应报冲突(否则噪声淹没真分歧)。');
106
+ });
107
+ it('空输入不崩', () => {
108
+ assert.deepStrictEqual(resolveEffective([], () => 'x'), { conflicts: [] });
109
+ });
110
+ it('★proof 必须记录主体与规则(§2 的「谁验的、按什么规则」)', () => {
111
+ const anchor = thresholdNode(V1);
112
+ const p = proofOf(anchor);
113
+ // 这两项是 ADR §2 明确要求 ProofIR 回答的问题;类型上是必填,
114
+ // 此处再钉一次运行时形态,防止将来被改成可选后悄悄缺失。
115
+ assert.ok(p.subject.kind && p.subject.by, 'subject 必须可追溯到具体主体。');
116
+ assert.ok(p.rule.id && p.rule.version, 'rule 必须带版本 —— 规则本身会演进,旧 proof 要说清当时用的哪一版。');
117
+ });
118
+ });
119
+ //# sourceMappingURL=proof-ir.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof-ir.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/proof-ir.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=quantity-ir.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quantity-ir.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/quantity-ir.test.ts"],"names":[],"mappings":""}