@aster-cloud/aster-lang-ts 1.0.28 → 1.0.30
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,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 类别名长度上限。★不是性能考虑——是防「LLM 把整段文本塞进 kind」这类输出,
|
|
3
|
+
* 它会让 reason 变成一大段不可控内容。合法类别名都很短(Role / Obligation / 角色)。
|
|
4
|
+
*/
|
|
5
|
+
const MAX_KIND_LENGTH = 64;
|
|
6
|
+
/**
|
|
7
|
+
* 类别名禁止的字符:HTML 标签符、引号、以及控制字符。
|
|
8
|
+
*
|
|
9
|
+
* <p>★挡的是注入载荷(`<img src=x onerror=...>`),而**不改写**合法值——
|
|
10
|
+
* 见 {@link parseProposals} 里的说明:转义会替 LLM 改写输出,违反 §12.4。
|
|
11
|
+
*/
|
|
12
|
+
const CONTROL_OR_MARKUP = /[<>"'\u0000-\u001f\u007f]/;
|
|
13
|
+
/**
|
|
14
|
+
* 单次提案的条目数上限。★防「LLM 返回巨量条目」耗尽下游内存——
|
|
15
|
+
* 实测 50000 条虽只需 40ms 解析,但每条都会进 rejected 数组并被持有。
|
|
16
|
+
* 真实文档的实体数远小于此。超出部分**如实报告**,不静默截断。
|
|
17
|
+
*/
|
|
18
|
+
const MAX_PROPOSALS = 1000;
|
|
19
|
+
const SYSTEM_PROMPT = `你是一个文本标注助手。给定一段政策文档,找出其中的**语义实体**
|
|
20
|
+
(角色、主体、义务等),逐条输出。
|
|
21
|
+
|
|
22
|
+
严格要求:
|
|
23
|
+
1. 只输出 JSON 数组,不要任何解释性文字、不要 Markdown 代码围栏。
|
|
24
|
+
2. 每条格式:{"text":"原文片段","kind":"类别","start":起始字符偏移}
|
|
25
|
+
3. "text" 必须是文档里**逐字节存在**的片段,不得改写、不得补全、不得翻译。
|
|
26
|
+
4. "start" 必须是该片段在文档中的**字符偏移**(从 0 开始)。
|
|
27
|
+
5. "kind" 用你认为最贴切的英文类别名(如 Role、Party、Obligation),不要臆造复杂结构。
|
|
28
|
+
6. **不确定就不要输出**。宁可少,不可错。
|
|
29
|
+
7. 不要输出金额、百分比、日期、时长——那些由另一个确定性模块处理。`;
|
|
30
|
+
/**
|
|
31
|
+
* 让 LLM 提出 Entity 候选。
|
|
32
|
+
*
|
|
33
|
+
* <p>★返回的每一条都会被**机械校验**:`text` 必须在 `document` 的声明位置上
|
|
34
|
+
* 逐字节存在。对不上的直接丢弃并计入 `rejected`——这是防 LLM 幻觉的第一道闸,
|
|
35
|
+
* 且它是**确定性**的,不依赖模型自觉。
|
|
36
|
+
*
|
|
37
|
+
* @param scope 可选:只在某个 SourceIR 节点范围内提出(如只看「## 审批」一节)
|
|
38
|
+
*/
|
|
39
|
+
export async function proposeEntities(document, provider, scope) {
|
|
40
|
+
const proposedBy = `llm:${provider.getName()}/${provider.getModel()}`;
|
|
41
|
+
const region = scope === undefined
|
|
42
|
+
? { start: 0, end: document.length }
|
|
43
|
+
: { start: scope.span.start, end: scope.span.end };
|
|
44
|
+
const text = document.slice(region.start, region.end);
|
|
45
|
+
const response = await provider.generate({
|
|
46
|
+
prompt: text,
|
|
47
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
48
|
+
// ★temperature 0:同一文档反复提出应得到同样结果。候选生成的**可复现性**
|
|
49
|
+
// 是审计的前提——否则「上次为什么提了这条」永远说不清。
|
|
50
|
+
temperature: 0,
|
|
51
|
+
});
|
|
52
|
+
return parseProposals(response.content, document, region.start, proposedBy);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 解析 LLM 的输出并**逐条机械校验**。
|
|
56
|
+
*
|
|
57
|
+
* <p>导出供测试直接调用——不必真的打 LLM 就能验证解析与校验逻辑。
|
|
58
|
+
*
|
|
59
|
+
* @param offsetBase scope 起点在原文中的偏移(LLM 看到的是切片,偏移要平移回去)
|
|
60
|
+
*/
|
|
61
|
+
export function parseProposals(raw, document, offsetBase, proposedBy) {
|
|
62
|
+
const candidates = [];
|
|
63
|
+
const rejected = [];
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(stripCodeFence(raw));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return { candidates: [], rejected: [{ raw, why: '整体不是合法 JSON' }], proposedBy };
|
|
70
|
+
}
|
|
71
|
+
if (!Array.isArray(parsed)) {
|
|
72
|
+
return { candidates: [], rejected: [{ raw, why: '顶层不是数组' }], proposedBy };
|
|
73
|
+
}
|
|
74
|
+
if (parsed.length > MAX_PROPOSALS) {
|
|
75
|
+
// ★不静默截断:调用方必须知道「LLM 返回量异常」这件事。
|
|
76
|
+
return {
|
|
77
|
+
candidates: [],
|
|
78
|
+
rejected: [{ raw: `<${parsed.length} 条>`, why: `条目数超上限(> ${MAX_PROPOSALS})——整批拒绝` }],
|
|
79
|
+
proposedBy,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
for (const item of parsed) {
|
|
83
|
+
const asText = JSON.stringify(item);
|
|
84
|
+
if (item === null || typeof item !== 'object') {
|
|
85
|
+
rejected.push({ raw: asText, why: '条目不是对象' });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const o = item;
|
|
89
|
+
if (typeof o.text !== 'string' || o.text.length === 0
|
|
90
|
+
|| typeof o.kind !== 'string' || o.kind.length === 0
|
|
91
|
+
|| typeof o.start !== 'number' || !Number.isInteger(o.start)) {
|
|
92
|
+
rejected.push({ raw: asText, why: '缺少 text/kind/start 或类型不对' });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// ★`kind` 是 **LLM 完全可控**的自由字符串,且会原样进入 `reason`
|
|
96
|
+
// (人类可读文本,可能被 UI 渲染)。本仓有 6 处
|
|
97
|
+
// `dangerouslySetInnerHTML`——一旦有人把 reason 接进去就是存储型 XSS。
|
|
98
|
+
//
|
|
99
|
+
// ★但**不能**在这里做 HTML 转义:转义会改变 kind 的值,而 §12.4 的
|
|
100
|
+
// 约定是「proposedKind 原样保留,由第③段的人判断」。转义等于替 LLM
|
|
101
|
+
// 改写了它的输出。
|
|
102
|
+
//
|
|
103
|
+
// 正确做法是**在源头限制形态**:类别名不该包含标签字符与控制字符。
|
|
104
|
+
// 这既挡住注入载荷,又不改变任何合法类别的值(Role/Party/Obligation/
|
|
105
|
+
// 中文类别名都不含这些字符)。渲染方仍应自行转义——纵深防御。
|
|
106
|
+
if (CONTROL_OR_MARKUP.test(o.kind)) {
|
|
107
|
+
rejected.push({ raw: asText, why: 'kind 含标签或控制字符——类别名不应包含这类字符' });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (o.kind.length > MAX_KIND_LENGTH) {
|
|
111
|
+
rejected.push({ raw: asText, why: `kind 过长(${o.kind.length} > ${MAX_KIND_LENGTH})` });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const start = offsetBase + o.start;
|
|
115
|
+
const end = start + o.text.length;
|
|
116
|
+
// ★核心闸门:声称的位置上必须**逐字节**是那段文本。
|
|
117
|
+
// LLM 最常见的幻觉是「文本对但位置错」或「位置对但文本被改写过」,
|
|
118
|
+
// 两者都会让后续的双向导航指向错误的地方。此处一律丢弃,不做纠正。
|
|
119
|
+
if (end > document.length || document.slice(start, end) !== o.text) {
|
|
120
|
+
rejected.push({
|
|
121
|
+
raw: asText,
|
|
122
|
+
why: `位置 ${start} 上的实际文本是 ${JSON.stringify(document.slice(start, end))},`
|
|
123
|
+
+ `与声称的 ${JSON.stringify(o.text)} 不符`,
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
candidates.push({
|
|
128
|
+
span: { start, end },
|
|
129
|
+
text: o.text,
|
|
130
|
+
proposedKind: o.kind,
|
|
131
|
+
proposedBy,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return { candidates, rejected, proposedBy };
|
|
135
|
+
}
|
|
136
|
+
/** LLM 常在 JSON 外面套 ```json 围栏,尽管 prompt 里已要求不要。 */
|
|
137
|
+
function stripCodeFence(s) {
|
|
138
|
+
const m = /^\s*```(?:json)?\s*\n([\s\S]*?)\n\s*```\s*$/.exec(s);
|
|
139
|
+
return m === null ? s.trim() : m[1].trim();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 对 Entity 候选的**确定性判定** —— 三段式的第②段。
|
|
143
|
+
*
|
|
144
|
+
* <p>★**恒返回 `REVIEW_REQUIRED`**,这不是偷懒,是 ADR §3/§5.1 的直接结论:
|
|
145
|
+
*
|
|
146
|
+
* - Entity 的目标是**语义**对应(「财务经理」↔ `Role.FinanceManager`),
|
|
147
|
+
* 而 verifier 只能读 `kind`/`value`/`name`/`origin`/`nodeId`——
|
|
148
|
+
* **类型层是两引擎合法分叉的层,不在可依赖范围内**(ADR §5.1)。
|
|
149
|
+
* - 机器能做的只有「这段文本确实存在于此」(已在 {@link parseProposals} 做过),
|
|
150
|
+
* 证明不了「它确实指代那个角色」。
|
|
151
|
+
*
|
|
152
|
+
* <p>★把它写成一个**显式函数**而不是省略,是为了让三段式在代码里可见:
|
|
153
|
+
* 谁也不能跳过第②段直接把 LLM 输出当结论。
|
|
154
|
+
*/
|
|
155
|
+
export function verifyEntityCandidate(candidate) {
|
|
156
|
+
return {
|
|
157
|
+
verdict: 'REVIEW_REQUIRED',
|
|
158
|
+
reason: `Entity「${candidate.text}」的类别 ${candidate.proposedKind} 属**语义**判断,`
|
|
159
|
+
+ '机器只能确认该文本存在于声称位置,证明不了它指代该实体 —— 须领域专家确认。',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=entity-proposer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-proposer.js","sourceRoot":"","sources":["../../../src/mapping/entity-proposer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MappingIR —— 「人类文本片段 ↔ Core IR 节点」的可验证映射(ADR 0037 §4/§7)。
|
|
3
|
+
*
|
|
4
|
+
* <h2>本模块的范围:只做「机器能证明的那一类」</h2>
|
|
5
|
+
*
|
|
6
|
+
* ADR §3 定的分工是:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* LLM / 启发式 / 人 → 提出候选映射(candidate)
|
|
10
|
+
* 确定性 verifier → 判定 verified / review-required / rejected
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* **AI 可以提出映射,但不能定义什么叫正确。** 本模块实现的是那个
|
|
14
|
+
* **确定性 verifier**,且只覆盖「能机械证明」的一类:**精确值字面量**
|
|
15
|
+
* (`"$10,000"` ↔ `Decimal("10000")`)。
|
|
16
|
+
*
|
|
17
|
+
* 带业务含义的映射(`"requires approval"` ↔ `RequireApprovalBeforeExecution`)
|
|
18
|
+
* 本模块一律判 `REVIEW_REQUIRED` —— **不猜**。这与 §3 的
|
|
19
|
+
* 「Human reviews only what machines cannot prove」一致。
|
|
20
|
+
*
|
|
21
|
+
* <h2>★硬约束:只能读两引擎已经一致的那部分(ADR §5 / §5.1)</h2>
|
|
22
|
+
*
|
|
23
|
+
* 归一化(`ir-normalize`,与 parity 门禁同源)会剥掉 derived analysis:
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* type, ret, retType, typeParams, typeInferred, retTypeInferred,
|
|
27
|
+
* constraints, piiCategories, piiLevel, effectCaps, effectCapsExplicit, captures
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* 因此 verifier **只能**依赖:`kind`、`value`、`name`、`origin`、`nodeId`。
|
|
31
|
+
*
|
|
32
|
+
* ★ADR §7 原文举的 `"$10,000" ↔ Money(10000)` **不可验证**——`Money` 是
|
|
33
|
+
* **类型**,而类型层是两引擎合法分叉的层。可验证的写法是
|
|
34
|
+
* `"$10,000" ↔ Decimal("10000")`(`Decimal` 是**节点 kind**,归一化后保留)。
|
|
35
|
+
*
|
|
36
|
+
* <h2>★第二个坑:canonical 形态 ≠ 源码文本</h2>
|
|
37
|
+
*
|
|
38
|
+
* `100.00m` 在 IR 里是 `{kind:"Decimal", value:"100"}`——尾随零被规范化掉。
|
|
39
|
+
* 故**不能做字符串相等比较**,必须按值比较。
|
|
40
|
+
*/
|
|
41
|
+
/** 人类文本里的一段(字符偏移,半开区间 `[start, end)`)。 */
|
|
42
|
+
export interface TextSpan {
|
|
43
|
+
readonly start: number;
|
|
44
|
+
readonly end: number;
|
|
45
|
+
}
|
|
46
|
+
/** 一条**候选**映射:由 LLM / 启发式 / 人提出,尚未判定。 */
|
|
47
|
+
export interface CandidateMapping {
|
|
48
|
+
/** 人类 artifact 里的文本片段位置。 */
|
|
49
|
+
readonly span: TextSpan;
|
|
50
|
+
/** 该片段的原文(冗余存一份,便于 verifier 独立核对,不依赖调用方重新切片)。 */
|
|
51
|
+
readonly text: string;
|
|
52
|
+
/** 目标 Core IR 节点的稳定标识(`NodeIdMap` 的 nodeId)。 */
|
|
53
|
+
readonly nodeId: string;
|
|
54
|
+
}
|
|
55
|
+
export type VerificationVerdict =
|
|
56
|
+
/** 机器已证明:文本与目标节点的值精确对应。 */
|
|
57
|
+
'VERIFIED'
|
|
58
|
+
/** 机器无法证明,需要人(领域专家 / 程序员)确认。**不是**「错」。 */
|
|
59
|
+
| 'REVIEW_REQUIRED'
|
|
60
|
+
/** 机器已证伪:文本与目标节点的值**矛盾**。 */
|
|
61
|
+
| 'REJECTED';
|
|
62
|
+
export interface VerificationResult {
|
|
63
|
+
readonly mapping: CandidateMapping;
|
|
64
|
+
readonly verdict: VerificationVerdict;
|
|
65
|
+
/** 判定依据(人类可读),**始终**给出——包括 VERIFIED,便于审计复核。 */
|
|
66
|
+
readonly reason: string;
|
|
67
|
+
/** 目标节点的 kind(若节点存在)。 */
|
|
68
|
+
readonly nodeKind?: string;
|
|
69
|
+
}
|
|
70
|
+
/** verifier 需要的最小节点信息——**刻意**只暴露合法字段,从类型上挡住误用。 */
|
|
71
|
+
export interface VerifiableNode {
|
|
72
|
+
readonly kind: string;
|
|
73
|
+
/** 字面量的值;非字面量节点为 `undefined`。 */
|
|
74
|
+
readonly value?: unknown;
|
|
75
|
+
/** 声明/引用的名字;无名节点为 `undefined`。 */
|
|
76
|
+
readonly name?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 验证一条候选映射。
|
|
80
|
+
*
|
|
81
|
+
* @param mapping 候选映射
|
|
82
|
+
* @param resolve 按 nodeId 取节点;返回 `undefined` 表示节点不存在
|
|
83
|
+
*/
|
|
84
|
+
export declare function verifyMapping(mapping: CandidateMapping, resolve: (nodeId: string) => VerifiableNode | undefined): VerificationResult;
|
|
85
|
+
//# sourceMappingURL=mapping-ir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping-ir.d.ts","sourceRoot":"","sources":["../../../src/mapping/mapping-ir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,mBAAmB;AAC7B,2BAA2B;AACzB,UAAU;AACZ,0CAA0C;GACxC,iBAAiB;AACnB,6BAA6B;GAC3B,UAAU,CAAC;AAEf,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,kCAAkC;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,GACtD,kBAAkB,CA4CpB"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MappingIR —— 「人类文本片段 ↔ Core IR 节点」的可验证映射(ADR 0037 §4/§7)。
|
|
3
|
+
*
|
|
4
|
+
* <h2>本模块的范围:只做「机器能证明的那一类」</h2>
|
|
5
|
+
*
|
|
6
|
+
* ADR §3 定的分工是:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* LLM / 启发式 / 人 → 提出候选映射(candidate)
|
|
10
|
+
* 确定性 verifier → 判定 verified / review-required / rejected
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* **AI 可以提出映射,但不能定义什么叫正确。** 本模块实现的是那个
|
|
14
|
+
* **确定性 verifier**,且只覆盖「能机械证明」的一类:**精确值字面量**
|
|
15
|
+
* (`"$10,000"` ↔ `Decimal("10000")`)。
|
|
16
|
+
*
|
|
17
|
+
* 带业务含义的映射(`"requires approval"` ↔ `RequireApprovalBeforeExecution`)
|
|
18
|
+
* 本模块一律判 `REVIEW_REQUIRED` —— **不猜**。这与 §3 的
|
|
19
|
+
* 「Human reviews only what machines cannot prove」一致。
|
|
20
|
+
*
|
|
21
|
+
* <h2>★硬约束:只能读两引擎已经一致的那部分(ADR §5 / §5.1)</h2>
|
|
22
|
+
*
|
|
23
|
+
* 归一化(`ir-normalize`,与 parity 门禁同源)会剥掉 derived analysis:
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* type, ret, retType, typeParams, typeInferred, retTypeInferred,
|
|
27
|
+
* constraints, piiCategories, piiLevel, effectCaps, effectCapsExplicit, captures
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* 因此 verifier **只能**依赖:`kind`、`value`、`name`、`origin`、`nodeId`。
|
|
31
|
+
*
|
|
32
|
+
* ★ADR §7 原文举的 `"$10,000" ↔ Money(10000)` **不可验证**——`Money` 是
|
|
33
|
+
* **类型**,而类型层是两引擎合法分叉的层。可验证的写法是
|
|
34
|
+
* `"$10,000" ↔ Decimal("10000")`(`Decimal` 是**节点 kind**,归一化后保留)。
|
|
35
|
+
*
|
|
36
|
+
* <h2>★第二个坑:canonical 形态 ≠ 源码文本</h2>
|
|
37
|
+
*
|
|
38
|
+
* `100.00m` 在 IR 里是 `{kind:"Decimal", value:"100"}`——尾随零被规范化掉。
|
|
39
|
+
* 故**不能做字符串相等比较**,必须按值比较。
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* 能被机械验证的字面量 kind,及其**值比较**方式。
|
|
43
|
+
*
|
|
44
|
+
* ★`Double` **不在**此列:它在 IR 里用 JS `number` 承载,源码文本与 IR 值
|
|
45
|
+
* **不可逆**(实测 `1.0` → `1`、`1e3` → `1000`)。机器无法证明「这段文本
|
|
46
|
+
* 就是这个 Double」,故一律交人复核,而不是用一个近似规则假装能证。
|
|
47
|
+
*/
|
|
48
|
+
const EXACT_VALUE_KINDS = new Set(['Int', 'Long', 'Decimal', 'String', 'Bool', 'PatInt']);
|
|
49
|
+
/**
|
|
50
|
+
* 验证一条候选映射。
|
|
51
|
+
*
|
|
52
|
+
* @param mapping 候选映射
|
|
53
|
+
* @param resolve 按 nodeId 取节点;返回 `undefined` 表示节点不存在
|
|
54
|
+
*/
|
|
55
|
+
export function verifyMapping(mapping, resolve) {
|
|
56
|
+
const span = mapping.span;
|
|
57
|
+
if (!Number.isInteger(span.start) || !Number.isInteger(span.end) || span.start < 0
|
|
58
|
+
|| span.end <= span.start) {
|
|
59
|
+
return result(mapping, 'REJECTED', `文本区间非法:[${span.start}, ${span.end}) —— 必须是 start ≥ 0 且 end > start 的整数区间。`);
|
|
60
|
+
}
|
|
61
|
+
if (mapping.text.length !== span.end - span.start) {
|
|
62
|
+
// ★区间长度与文本长度必须自洽,否则 span 指向的根本不是这段文本。
|
|
63
|
+
// 不自洽时**不能**默默采信 text —— 那会让「双向导航」跳到错误位置。
|
|
64
|
+
return result(mapping, 'REJECTED', `text 长度 ${mapping.text.length} 与区间宽度 ${span.end - span.start} 不符。`);
|
|
65
|
+
}
|
|
66
|
+
const node = resolve(mapping.nodeId);
|
|
67
|
+
if (node === undefined) {
|
|
68
|
+
return result(mapping, 'REJECTED', `目标节点不存在:${mapping.nodeId}`);
|
|
69
|
+
}
|
|
70
|
+
if (!EXACT_VALUE_KINDS.has(node.kind)) {
|
|
71
|
+
// 非精确值节点(Call / If / Func / Double …):机器证不了,交人。
|
|
72
|
+
// ★这**不是**失败。ADR §3:Human reviews only what machines cannot prove。
|
|
73
|
+
return result(mapping, 'REVIEW_REQUIRED', `目标是 ${node.kind} 节点,不属于可机械证明的精确值字面量`
|
|
74
|
+
+ `(${[...EXACT_VALUE_KINDS].join('/')})——需人工确认语义对应关系。`, node.kind);
|
|
75
|
+
}
|
|
76
|
+
const literal = parseLiteralFromText(mapping.text, node.kind);
|
|
77
|
+
if (literal === undefined) {
|
|
78
|
+
return result(mapping, 'REVIEW_REQUIRED', `无法从文本「${mapping.text}」中机械解析出 ${node.kind} 值——需人工确认。`, node.kind);
|
|
79
|
+
}
|
|
80
|
+
if (valuesEqual(literal, node.value, node.kind)) {
|
|
81
|
+
return result(mapping, 'VERIFIED', `文本「${mapping.text}」解析为 ${node.kind} ${JSON.stringify(literal)},`
|
|
82
|
+
+ `与目标节点的值一致。`, node.kind);
|
|
83
|
+
}
|
|
84
|
+
return result(mapping, 'REJECTED', `文本「${mapping.text}」解析为 ${JSON.stringify(literal)},`
|
|
85
|
+
+ `但目标节点的值是 ${JSON.stringify(node.value)} —— 两者矛盾。`, node.kind);
|
|
86
|
+
}
|
|
87
|
+
function result(mapping, verdict, reason, nodeKind) {
|
|
88
|
+
return { mapping, verdict, reason, ...(nodeKind === undefined ? {} : { nodeKind }) };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 从人类文本里机械解析出字面量值。
|
|
92
|
+
*
|
|
93
|
+
* <p>返回 `undefined` = 解析不出来(→ 交人复核),**不是**解析成错的值。
|
|
94
|
+
*
|
|
95
|
+
* <p>★容许人类书写惯例(货币符号、千分位、前后空白),因为这正是
|
|
96
|
+
* MappingIR 要跨越的鸿沟:`"$10,000"` 与 `10000` 之间那一步。
|
|
97
|
+
*/
|
|
98
|
+
function parseLiteralFromText(text, kind) {
|
|
99
|
+
const t = text.trim();
|
|
100
|
+
if (t.length === 0)
|
|
101
|
+
return undefined;
|
|
102
|
+
switch (kind) {
|
|
103
|
+
case 'Bool': {
|
|
104
|
+
const lower = t.toLowerCase();
|
|
105
|
+
if (lower === 'true' || lower === 'yes')
|
|
106
|
+
return true;
|
|
107
|
+
if (lower === 'false' || lower === 'no')
|
|
108
|
+
return false;
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
case 'String': {
|
|
112
|
+
// 带引号则剥掉引号;否则取原文。★不做 trim 之外的任何改写——
|
|
113
|
+
// 字符串的值是什么就是什么,规范化会制造假匹配。
|
|
114
|
+
if (t.length >= 2 && ((t[0] === '"' && t.endsWith('"'))
|
|
115
|
+
|| (t[0] === "'" && t.endsWith("'")))) {
|
|
116
|
+
return t.slice(1, -1);
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
case 'Int':
|
|
121
|
+
case 'Long':
|
|
122
|
+
case 'PatInt':
|
|
123
|
+
case 'Decimal': {
|
|
124
|
+
const numeric = stripHumanNumberDecorations(t);
|
|
125
|
+
if (numeric === undefined)
|
|
126
|
+
return undefined;
|
|
127
|
+
return numeric;
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 剥掉人类数字书写的装饰:货币符号、千分位逗号、正号。
|
|
135
|
+
*
|
|
136
|
+
* <p>返回**规范化后的十进制字符串**(保留符号与小数点),无法识别时返回
|
|
137
|
+
* `undefined`。★不转成 JS number —— Long/Decimal 超出 number 安全范围时
|
|
138
|
+
* 会静默丢精度(`9007199254740993` → `…992`,本仓已实测踩过)。
|
|
139
|
+
*/
|
|
140
|
+
function stripHumanNumberDecorations(t) {
|
|
141
|
+
// 允许:可选货币符号 → 可选正负号 → 数字(可含千分位逗号)→ 可选小数部分
|
|
142
|
+
const m = /^[$€£¥]?\s*([+-]?)(\d{1,3}(?:,\d{3})+|\d+)(?:\.(\d+))?$/.exec(t);
|
|
143
|
+
if (m === null)
|
|
144
|
+
return undefined;
|
|
145
|
+
const sign = m[1] === '-' ? '-' : '';
|
|
146
|
+
const intPart = m[2].replace(/,/g, '');
|
|
147
|
+
const frac = m[3];
|
|
148
|
+
return frac === undefined ? `${sign}${intPart}` : `${sign}${intPart}.${frac}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 按**值**比较,不按文本。
|
|
152
|
+
*
|
|
153
|
+
* <p>★`100.00m` 在 IR 里是 `value:"100"`(尾随零被规范化)。若做字符串相等,
|
|
154
|
+
* 文本 `"$10,000.00"` 会验不过一个数值上完全正确的 `Decimal("10000")`。
|
|
155
|
+
*/
|
|
156
|
+
function valuesEqual(fromText, fromNode, kind) {
|
|
157
|
+
if (kind === 'Bool')
|
|
158
|
+
return fromText === fromNode;
|
|
159
|
+
if (kind === 'String')
|
|
160
|
+
return fromText === fromNode;
|
|
161
|
+
// 数值类:两侧都规范成十进制字符串再比,全程不经过 JS number(避免精度丢失)。
|
|
162
|
+
const a = canonicalDecimalString(fromText);
|
|
163
|
+
const b = canonicalDecimalString(fromNode);
|
|
164
|
+
return a !== undefined && b !== undefined && a === b;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 把数值规范成可比较的十进制字符串:去尾随零、去多余前导零、`-0` → `0`。
|
|
168
|
+
*
|
|
169
|
+
* <p>全程字符串运算,**不经过 JS number** —— 超安全整数范围的 Long
|
|
170
|
+
* 一旦过一次 number 就会静默丢精度。
|
|
171
|
+
*/
|
|
172
|
+
function canonicalDecimalString(v) {
|
|
173
|
+
let s;
|
|
174
|
+
if (typeof v === 'string')
|
|
175
|
+
s = v.trim();
|
|
176
|
+
else if (typeof v === 'number') {
|
|
177
|
+
if (!Number.isFinite(v))
|
|
178
|
+
return undefined;
|
|
179
|
+
s = String(v);
|
|
180
|
+
}
|
|
181
|
+
else if (typeof v === 'bigint')
|
|
182
|
+
s = v.toString();
|
|
183
|
+
else
|
|
184
|
+
return undefined;
|
|
185
|
+
const m = /^([+-]?)(\d+)(?:\.(\d*))?$/.exec(s);
|
|
186
|
+
if (m === null)
|
|
187
|
+
return undefined;
|
|
188
|
+
const sign = m[1] === '-' ? '-' : '';
|
|
189
|
+
const intPart = m[2].replace(/^0+(?=\d)/, '');
|
|
190
|
+
const frac = (m[3] ?? '').replace(/0+$/, '');
|
|
191
|
+
const body = frac.length > 0 ? `${intPart}.${frac}` : intPart;
|
|
192
|
+
// -0 / -0.0 归一为 0
|
|
193
|
+
return /^0(?:\.0*)?$/.test(body) ? intPart : `${sign}${body}`;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=mapping-ir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapping-ir.js","sourceRoot":"","sources":["../../../src/mapping/mapping-ir.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { CandidateMapping, VerificationVerdict } from './mapping-ir.js';
|
|
2
|
+
/**
|
|
3
|
+
* ProofIR —— 「这条映射为什么成立」的**不可变**记录(ADR 0037 §2/§6.1)。
|
|
4
|
+
*
|
|
5
|
+
* <h2>回答 ADR §2 提出的五个问题</h2>
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* 为什么认为这条映射成立? → rule(判定规则)+ reason(依据)
|
|
9
|
+
* 谁验证的? → subject(主体)
|
|
10
|
+
* 使用什么规则? → rule
|
|
11
|
+
* 对应哪个版本? → verifiedAgainst.contentHash
|
|
12
|
+
* 现在是否仍然有效? → isApplicableTo()(算出来,不是存出来)
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* <h2>★失效语义:不可变 + 水位线(ADR §6.1)</h2>
|
|
16
|
+
*
|
|
17
|
+
* 沿用本仓既有先例(BYOK 的 `byokQuotaResetAt` 水位线、`audit-log` 的只追加):
|
|
18
|
+
*
|
|
19
|
+
* ```
|
|
20
|
+
* proof 记录本身 不可变、只追加 ← 「当时确实验证过」是历史事实
|
|
21
|
+
* proof 的有效性 由内容指纹算出 ← 不改写历史,只判定「对当前版本是否适用」
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ★**为什么不能原地标 stale**:proof 的价值就在于「在某个确定版本上、由某个
|
|
25
|
+
* 确定主体、按某条确定规则验证过」。原地改写会**销毁这条历史事实**——下次
|
|
26
|
+
* 审计无法回答「那次到底验没验过」。这与本仓审计链不可改写的既有立场一致。
|
|
27
|
+
*/
|
|
28
|
+
/** 谁做出的判定。 */
|
|
29
|
+
export type ProofSubject =
|
|
30
|
+
/** 确定性 verifier(机器证明)。`by` 记模块标识与算法版本。 */
|
|
31
|
+
{
|
|
32
|
+
readonly kind: 'VERIFIER';
|
|
33
|
+
readonly by: string;
|
|
34
|
+
}
|
|
35
|
+
/** 领域专家(业务语义确认)。`by` 记可追溯的身份标识。 */
|
|
36
|
+
| {
|
|
37
|
+
readonly kind: 'DOMAIN_EXPERT';
|
|
38
|
+
readonly by: string;
|
|
39
|
+
}
|
|
40
|
+
/** 程序员(技术语义确认)。 */
|
|
41
|
+
| {
|
|
42
|
+
readonly kind: 'ENGINEER';
|
|
43
|
+
readonly by: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* 判定所依据的规则。
|
|
47
|
+
*
|
|
48
|
+
* <p>★与 `subject` **分开**记录:同一个主体可以按不同规则做判定,而「按哪条
|
|
49
|
+
* 规则」恰恰是审计时最需要回答的。把它揉进 subject 会让两者都说不清楚。
|
|
50
|
+
*/
|
|
51
|
+
export interface ProofRule {
|
|
52
|
+
/** 规则标识,如 `exact-value` / `domain-review` 。 */
|
|
53
|
+
readonly id: string;
|
|
54
|
+
/** 规则版本——规则本身会演进,旧 proof 必须能说清「当时用的是哪一版」。 */
|
|
55
|
+
readonly version: string;
|
|
56
|
+
}
|
|
57
|
+
/** proof 锚定的目标:某个版本的某个节点。 */
|
|
58
|
+
export interface ProofAnchor {
|
|
59
|
+
readonly nodeId: string;
|
|
60
|
+
/** 做出判定时该节点子树的内容指纹(`NodeIdMap` 的 contentHash)。 */
|
|
61
|
+
readonly contentHash: string;
|
|
62
|
+
}
|
|
63
|
+
/** 一条**不可变**的 proof 记录。 */
|
|
64
|
+
export interface Proof {
|
|
65
|
+
readonly mapping: CandidateMapping;
|
|
66
|
+
readonly verdict: VerificationVerdict;
|
|
67
|
+
readonly subject: ProofSubject;
|
|
68
|
+
readonly rule: ProofRule;
|
|
69
|
+
readonly verifiedAgainst: ProofAnchor;
|
|
70
|
+
/** 人类可读依据(verifier 的 reason,或人工复核的说明)。 */
|
|
71
|
+
readonly reason: string;
|
|
72
|
+
/**
|
|
73
|
+
* 记录时间(ISO 8601)。★由调用方传入而非本模块取系统时钟——
|
|
74
|
+
* 取时钟会让同一输入产出不同记录,无法复现、无法测试。
|
|
75
|
+
*/
|
|
76
|
+
readonly recordedAt: string;
|
|
77
|
+
}
|
|
78
|
+
/** proof 对当前版本是否仍适用。 */
|
|
79
|
+
export type Applicability =
|
|
80
|
+
/** 目标节点内容未变 → 当时的判定对现在仍然成立。 */
|
|
81
|
+
{
|
|
82
|
+
readonly applicable: true;
|
|
83
|
+
}
|
|
84
|
+
/** 目标节点内容已变 → 需重新验证。**不代表**当时的判定是错的。 */
|
|
85
|
+
| {
|
|
86
|
+
readonly applicable: false;
|
|
87
|
+
readonly why: 'CONTENT_CHANGED';
|
|
88
|
+
readonly currentHash: string;
|
|
89
|
+
}
|
|
90
|
+
/** 目标节点已不存在 → 需重新验证。 */
|
|
91
|
+
| {
|
|
92
|
+
readonly applicable: false;
|
|
93
|
+
readonly why: 'NODE_GONE';
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* 判定一条 proof 对**当前**版本是否仍适用。
|
|
97
|
+
*
|
|
98
|
+
* <p>★这是**算出来**的,不是存出来的——proof 记录本身永不修改。
|
|
99
|
+
*
|
|
100
|
+
* @param currentHashOf 按 nodeId 取当前 contentHash;`undefined` 表示节点已不存在
|
|
101
|
+
*/
|
|
102
|
+
export declare function isApplicableTo(proof: Proof, currentHashOf: (nodeId: string) => string | undefined): Applicability;
|
|
103
|
+
/**
|
|
104
|
+
* 从一组 proof 中选出对当前版本**有效**的那条。
|
|
105
|
+
*
|
|
106
|
+
* <h2>★仲裁规则(本仓当前的决定)</h2>
|
|
107
|
+
*
|
|
108
|
+
* 同一个 `(span, nodeId)` 上可能存在多条 proof(机器验过一次、专家又复核过一次、
|
|
109
|
+
* 内容变了之后重新验过…)。规则:
|
|
110
|
+
*
|
|
111
|
+
* 1. **只考虑对当前版本仍适用的**(内容指纹匹配)——不适用的直接出局。
|
|
112
|
+
* 2. 在仍适用的里面,**取 `recordedAt` 最新的一条**。
|
|
113
|
+
*
|
|
114
|
+
* ★**为什么不按主体优先级**(例如「专家 > 机器」):那需要先定义一个跨主体的
|
|
115
|
+
* 权威序,而这是**产品/合规决策**,不是技术决策。在它被明确之前,按时间取最新
|
|
116
|
+
* 是唯一不需要额外假设的规则——「最近一次复核的结论」本身就是一个可辩护的默认。
|
|
117
|
+
*
|
|
118
|
+
* <p>⚠️ 若将来引入主体优先级,必须同时回答:机器判 REJECTED 而专家判 VERIFIED
|
|
119
|
+
* 时谁赢?本模块**刻意不猜**——`conflicts` 字段把并存的分歧如实暴露给调用方。
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveEffective(proofs: readonly Proof[], currentHashOf: (nodeId: string) => string | undefined): {
|
|
122
|
+
readonly effective?: Proof;
|
|
123
|
+
readonly conflicts: readonly Proof[];
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=proof-ir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-ir.d.ts","sourceRoot":"","sources":["../../../src/mapping/proof-ir.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,cAAc;AACd,MAAM,MAAM,YAAY;AACtB,0CAA0C;AACxC;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AACpD,mCAAmC;GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AACzD,mBAAmB;GACjB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,6BAA6B;AAC7B,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,2BAA2B;AAC3B,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC;IACtC,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAwB;AACxB,MAAM,MAAM,aAAa;AACvB,+BAA+B;AAC7B;IAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAA;CAAE;AAC/B,wCAAwC;GACtC;IAAE,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AAC/F,wBAAwB;GACtB;IAAE,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;CAAE,CAAC;AAE9D;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACpD,aAAa,CASf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACpD;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,KAAK,EAAE,CAAA;CAAE,CAatE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 判定一条 proof 对**当前**版本是否仍适用。
|
|
3
|
+
*
|
|
4
|
+
* <p>★这是**算出来**的,不是存出来的——proof 记录本身永不修改。
|
|
5
|
+
*
|
|
6
|
+
* @param currentHashOf 按 nodeId 取当前 contentHash;`undefined` 表示节点已不存在
|
|
7
|
+
*/
|
|
8
|
+
export function isApplicableTo(proof, currentHashOf) {
|
|
9
|
+
const current = currentHashOf(proof.verifiedAgainst.nodeId);
|
|
10
|
+
if (current === undefined) {
|
|
11
|
+
return { applicable: false, why: 'NODE_GONE' };
|
|
12
|
+
}
|
|
13
|
+
if (current !== proof.verifiedAgainst.contentHash) {
|
|
14
|
+
return { applicable: false, why: 'CONTENT_CHANGED', currentHash: current };
|
|
15
|
+
}
|
|
16
|
+
return { applicable: true };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 从一组 proof 中选出对当前版本**有效**的那条。
|
|
20
|
+
*
|
|
21
|
+
* <h2>★仲裁规则(本仓当前的决定)</h2>
|
|
22
|
+
*
|
|
23
|
+
* 同一个 `(span, nodeId)` 上可能存在多条 proof(机器验过一次、专家又复核过一次、
|
|
24
|
+
* 内容变了之后重新验过…)。规则:
|
|
25
|
+
*
|
|
26
|
+
* 1. **只考虑对当前版本仍适用的**(内容指纹匹配)——不适用的直接出局。
|
|
27
|
+
* 2. 在仍适用的里面,**取 `recordedAt` 最新的一条**。
|
|
28
|
+
*
|
|
29
|
+
* ★**为什么不按主体优先级**(例如「专家 > 机器」):那需要先定义一个跨主体的
|
|
30
|
+
* 权威序,而这是**产品/合规决策**,不是技术决策。在它被明确之前,按时间取最新
|
|
31
|
+
* 是唯一不需要额外假设的规则——「最近一次复核的结论」本身就是一个可辩护的默认。
|
|
32
|
+
*
|
|
33
|
+
* <p>⚠️ 若将来引入主体优先级,必须同时回答:机器判 REJECTED 而专家判 VERIFIED
|
|
34
|
+
* 时谁赢?本模块**刻意不猜**——`conflicts` 字段把并存的分歧如实暴露给调用方。
|
|
35
|
+
*/
|
|
36
|
+
export function resolveEffective(proofs, currentHashOf) {
|
|
37
|
+
const applicable = proofs.filter(p => isApplicableTo(p, currentHashOf).applicable);
|
|
38
|
+
if (applicable.length === 0) {
|
|
39
|
+
return { conflicts: [] };
|
|
40
|
+
}
|
|
41
|
+
// 按时间降序;时间相同则保持输入顺序(稳定排序),避免引入隐式随机性。
|
|
42
|
+
const sorted = [...applicable].sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
43
|
+
const effective = sorted[0];
|
|
44
|
+
// 结论不同的其余 proof = 真实分歧,如实暴露,不静默吞掉。
|
|
45
|
+
const conflicts = sorted.slice(1).filter(p => p.verdict !== effective.verdict);
|
|
46
|
+
return { effective, conflicts };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=proof-ir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-ir.js","sourceRoot":"","sources":["../../../src/mapping/proof-ir.ts"],"names":[],"mappings":"AAmFA;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAY,EACZ,aAAqD;IAErD,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAC5D,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;IACjD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC;QAClD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAC7E,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAwB,EACxB,aAAqD;IAErD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC;IACnF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC3B,CAAC;IAED,qCAAqC;IACrC,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACxF,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;IAE7B,mCAAmC;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC;IAC/E,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAClC,CAAC"}
|