@aster-cloud/aster-lang-ts 1.0.34 → 1.0.35
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/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mapping/pipeline.d.ts +87 -0
- package/dist/src/mapping/pipeline.d.ts.map +1 -0
- package/dist/src/mapping/pipeline.js +145 -0
- package/dist/src/mapping/pipeline.js.map +1 -0
- package/dist/src/mapping/quantity-ir.js +68 -5
- package/dist/src/mapping/quantity-ir.js.map +1 -1
- package/dist/src/mapping/review-queue.d.ts +92 -0
- package/dist/src/mapping/review-queue.d.ts.map +1 -0
- package/dist/src/mapping/review-queue.js +105 -0
- package/dist/src/mapping/review-queue.js.map +1 -0
- package/dist/test/unit/mapping/bridge-parity.test.d.ts +2 -0
- package/dist/test/unit/mapping/bridge-parity.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/bridge-parity.test.js +74 -0
- package/dist/test/unit/mapping/bridge-parity.test.js.map +1 -0
- package/dist/test/unit/mapping/pipeline.test.d.ts +2 -0
- package/dist/test/unit/mapping/pipeline.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/pipeline.test.js +108 -0
- package/dist/test/unit/mapping/pipeline.test.js.map +1 -0
- package/dist/test/unit/mapping/review-queue.test.d.ts +2 -0
- package/dist/test/unit/mapping/review-queue.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/review-queue.test.js +112 -0
- package/dist/test/unit/mapping/review-queue.test.js.map +1 -0
- package/dist/test/unit/mapping/verdict-parity.test.d.ts +2 -0
- package/dist/test/unit/mapping/verdict-parity.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/verdict-parity.test.js +85 -0
- package/dist/test/unit/mapping/verdict-parity.test.js.map +1 -0
- package/package.json +1 -1
package/dist/src/index.d.ts
CHANGED
|
@@ -42,4 +42,18 @@ export type { ControlsRegistryData, LocalizedTitle } from './governance/controls
|
|
|
42
42
|
export { generateFieldValue, generateInputValues, getFieldValueHint, } from './parser/input-generator.js';
|
|
43
43
|
export type { TypeKind, FieldInfo, ParameterInfo, } from './parser/input-generator.js';
|
|
44
44
|
export type * from './types.js';
|
|
45
|
+
export { runSemanticBridge } from './mapping/pipeline.js';
|
|
46
|
+
export type { BridgeResult, VerifiedCandidate } from './mapping/pipeline.js';
|
|
47
|
+
export { parseSourceIr, nodeAtOffset, verifyCoverage } from './mapping/source-ir.js';
|
|
48
|
+
export type { SourceNode, SectionKind } from './mapping/source-ir.js';
|
|
49
|
+
export { extractQuantities } from './mapping/quantity-ir.js';
|
|
50
|
+
export type { Quantity, QuantityKind, EntityCandidate } from './mapping/quantity-ir.js';
|
|
51
|
+
export { verifyMapping } from './mapping/mapping-ir.js';
|
|
52
|
+
export type { CandidateMapping, VerificationResult, VerificationVerdict, VerifiableNode, TextSpan } from './mapping/mapping-ir.js';
|
|
53
|
+
export { buildReviewQueue, recordHumanProof } from './mapping/review-queue.js';
|
|
54
|
+
export type { ReviewQueue, ReviewItem, ReviewSource } from './mapping/review-queue.js';
|
|
55
|
+
export { isApplicableTo, resolveEffective } from './mapping/proof-ir.js';
|
|
56
|
+
export type { Proof, ProofSubject, ProofRule, ProofAnchor, Applicability } from './mapping/proof-ir.js';
|
|
57
|
+
export { proposeEntities, parseProposals, verifyEntityCandidate } from './mapping/entity-proposer.js';
|
|
58
|
+
export type { ProposalResult } from './mapping/entity-proposer.js';
|
|
45
59
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,cAAc,EACd,uBAAuB,GACxB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EACV,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpC,OAAO,EACL,eAAe,EACf,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvF,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAGnG,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,QAAQ,EACR,SAAS,EACT,aAAa,GACd,MAAM,6BAA6B,CAAC;AAGrC,mBAAmB,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,cAAc,EACd,uBAAuB,GACxB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EACV,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpC,OAAO,EACL,eAAe,EACf,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvF,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAGnG,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,QAAQ,EACR,SAAS,EACT,aAAa,GACd,MAAM,6BAA6B,CAAC;AAGrC,mBAAmB,YAAY,CAAC;AAMhC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EACrE,cAAc,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACtG,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -40,4 +40,15 @@ export { controlRegistryFrom, defaultControlRegistry } from './governance/contro
|
|
|
40
40
|
export { CONTROLS_REGISTRY } from './governance/controls-registry.data.js';
|
|
41
41
|
// 输入值生成器(用于策略执行时自动生成示例输入)
|
|
42
42
|
export { generateFieldValue, generateInputValues, getFieldValueHint, } from './parser/input-generator.js';
|
|
43
|
+
// ── ADR 0037 可验证语义桥 ────────────────────────────────────────────
|
|
44
|
+
// ★接线后才导出:此前六个模块全是死代码(生产调用数 0、不在导出面内)。
|
|
45
|
+
// `runSemanticBridge` 是唯一入口,内部串起
|
|
46
|
+
// SourceIR → QuantityIR → CandidateGenerator → MappingIR。
|
|
47
|
+
export { runSemanticBridge } from './mapping/pipeline.js';
|
|
48
|
+
export { parseSourceIr, nodeAtOffset, verifyCoverage } from './mapping/source-ir.js';
|
|
49
|
+
export { extractQuantities } from './mapping/quantity-ir.js';
|
|
50
|
+
export { verifyMapping } from './mapping/mapping-ir.js';
|
|
51
|
+
export { buildReviewQueue, recordHumanProof } from './mapping/review-queue.js';
|
|
52
|
+
export { isApplicableTo, resolveEffective } from './mapping/proof-ir.js';
|
|
53
|
+
export { proposeEntities, parseProposals, verifyEntityCandidate } from './mapping/entity-proposer.js';
|
|
43
54
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,UAAU;AACV,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,oBAAoB;AACpB,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,cAAc,EACd,uBAAuB,GACxB,MAAM,kCAAkC,CAAC;AAO1C,UAAU;AACV,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO;AACP,OAAO,EACL,eAAe,EACf,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAG3E,0BAA0B;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,UAAU;AACV,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,oBAAoB;AACpB,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,cAAc,EACd,uBAAuB,GACxB,MAAM,kCAAkC,CAAC;AAO1C,UAAU;AACV,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO;AACP,OAAO,EACL,eAAe,EACf,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAG3E,0BAA0B;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AAUrC,kEAAkE;AAClE,uCAAuC;AACvC,mCAAmC;AACnC,4DAA4D;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAErF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGxD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type SourceNode } from './source-ir.js';
|
|
2
|
+
import { type Quantity } from './quantity-ir.js';
|
|
3
|
+
import { type CandidateMapping, type VerificationResult } from './mapping-ir.js';
|
|
4
|
+
/**
|
|
5
|
+
* 可验证语义桥的**端到端接线**(ADR 0037 §7)。
|
|
6
|
+
*
|
|
7
|
+
* <h2>★为什么需要这个文件</h2>
|
|
8
|
+
*
|
|
9
|
+
* ADR 0037 建了六个模块(SourceIR / QuantityIR / MappingIR / ProofIR /
|
|
10
|
+
* CandidateGenerator / EntityProposer),但**没有任何生产路径调用它们**
|
|
11
|
+
* ——实测六个模块的生产调用数全为 0,也不在 `src/index.ts` 的导出面内。
|
|
12
|
+
* 即:**地基铺好了,线没接**。
|
|
13
|
+
*
|
|
14
|
+
* <p>本文件把它们串成一条可观测的链路:
|
|
15
|
+
*
|
|
16
|
+
* <pre>
|
|
17
|
+
* 人类文档
|
|
18
|
+
* ├─ parseSourceIr ──────────► 文档结构(Document/Section/Span)
|
|
19
|
+
* ├─ extractQuantities ──────► 数量实体(确定性,零 AI)
|
|
20
|
+
* └─ canonicalize→lex→parse→lower
|
|
21
|
+
* ├─ computeNodeIds ───► 稳定节点 ID
|
|
22
|
+
* ├─ generateCandidates► 候选映射(机械切片,不猜)
|
|
23
|
+
* └─ verifyMapping ────► 判定:VERIFIED / REVIEW_REQUIRED / REJECTED
|
|
24
|
+
* </pre>
|
|
25
|
+
*
|
|
26
|
+
* <h2>★本模块刻意不做的事</h2>
|
|
27
|
+
*
|
|
28
|
+
* - **不调 LLM**。Entity 提出是三段式的第①段,需要 provider,由调用方
|
|
29
|
+
* 显式传入结果(见 {@link BridgeResult.entityCandidates} 的说明)。
|
|
30
|
+
* 把 LLM 调用埋进"主链路"会让一条本该确定性的流水线变得不可复现。
|
|
31
|
+
* - **不产出 Proof**。`REVIEW_REQUIRED` 的候选必须由**人**给出 ProofIR
|
|
32
|
+
* (`subject = DOMAIN_EXPERT`),机器不得代签(ADR §3)。
|
|
33
|
+
* - **不吞错**。任何跳过/失败都进 {@link BridgeResult.diagnostics}。
|
|
34
|
+
*/
|
|
35
|
+
/** 一条候选及其判定结果。 */
|
|
36
|
+
export interface VerifiedCandidate {
|
|
37
|
+
readonly mapping: CandidateMapping;
|
|
38
|
+
readonly result: VerificationResult;
|
|
39
|
+
/**
|
|
40
|
+
* 目标节点在**本次判定时**的内容指纹(`NodeIdMap` 的 contentHash)。
|
|
41
|
+
*
|
|
42
|
+
* <p>★**必须随候选一起交出去**:人工复核记录 Proof 时要用它锚定
|
|
43
|
+
* 「当时看到的是哪一版内容」。内容一变,`isApplicableTo` 才能算出
|
|
44
|
+
* `CONTENT_CHANGED` 而不是悄悄沿用旧结论。
|
|
45
|
+
*
|
|
46
|
+
* <p>★这个字段是**接 UI 时补的**:此前 `BridgeResult` 不含它,
|
|
47
|
+
* 导致复核面板拿不到 hash、提交必被服务端拒(要求 64 位十六进制)。
|
|
48
|
+
* 单测全绿也发现不了——因为没有任何测试真的走完"取候选 → 提交结论"。
|
|
49
|
+
* 又一次「验了对象,没验连线」。
|
|
50
|
+
*
|
|
51
|
+
* <p>节点不在 NodeIdMap 中时为 `undefined`(正常情况下不会发生,
|
|
52
|
+
* 真发生了 diagnostics 里会有「路径口径可能漂移」)。
|
|
53
|
+
*/
|
|
54
|
+
readonly contentHash: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
/** 端到端结果。★所有"没做成"的事都在 `diagnostics` 里,不静默丢弃。 */
|
|
57
|
+
export interface BridgeResult {
|
|
58
|
+
/** 文档结构(人类视角)。源文本无法解析时为 `undefined`。 */
|
|
59
|
+
readonly sourceIr: SourceNode | undefined;
|
|
60
|
+
/** 确定性抽取的数量实体。 */
|
|
61
|
+
readonly quantities: readonly Quantity[];
|
|
62
|
+
/** 候选映射及其判定。 */
|
|
63
|
+
readonly verified: readonly VerifiedCandidate[];
|
|
64
|
+
/** 按判定分组的计数——UI 的复核队列直接用这个。 */
|
|
65
|
+
readonly summary: {
|
|
66
|
+
readonly verified: number;
|
|
67
|
+
readonly reviewRequired: number;
|
|
68
|
+
readonly rejected: number;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* 过程中所有「没做成」的事:解析失败、节点被跳过、区间切不出来……
|
|
72
|
+
*
|
|
73
|
+
* <p>★**必须如实报告**。静默跳过会让调用方以为「全都生成了」,
|
|
74
|
+
* 而实际上某些节点从双向导航里消失了——那种缺失极难发现。
|
|
75
|
+
*/
|
|
76
|
+
readonly diagnostics: readonly string[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 跑通「人类文档 → 结构化 → 抽取 → 候选 → 验证」全链路。
|
|
80
|
+
*
|
|
81
|
+
* <p>★`source` 必须是**可编译的 Aster 源码**。对于"任意文字",当前的定位是:
|
|
82
|
+
* SourceIR 与 QuantityIR 对任意文本都能工作(它们只依赖形态特征),
|
|
83
|
+
* 而候选映射与验证需要源码能 lower 成 Core IR。两者在本函数里**分别降级**
|
|
84
|
+
* ——编译不了时仍返回文档结构与数量实体,并在 diagnostics 里说明原因。
|
|
85
|
+
*/
|
|
86
|
+
export declare function runSemanticBridge(source: string): BridgeResult;
|
|
87
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../src/mapping/pipeline.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EAAiB,KAAK,gBAAgB,EAAE,KAAK,kBAAkB,EAC/C,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,kBAAkB;AAClB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IAC1C,kBAAkB;IAClB,QAAQ,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,gBAAgB;IAChB,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAgF9D"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { canonicalize } from '../frontend/canonicalizer.js';
|
|
2
|
+
import { lex } from '../frontend/lexer.js';
|
|
3
|
+
import { parse } from '../parser.js';
|
|
4
|
+
import { lowerModule } from '../lower_to_core.js';
|
|
5
|
+
import { computeNodeIds } from '../nodeid/node-id-map.js';
|
|
6
|
+
import { parseSourceIr } from './source-ir.js';
|
|
7
|
+
import { extractQuantities } from './quantity-ir.js';
|
|
8
|
+
import { generateCandidates, collectLiteralNodes } from './candidate-generator.js';
|
|
9
|
+
import { verifyMapping } from './mapping-ir.js';
|
|
10
|
+
/**
|
|
11
|
+
* 跑通「人类文档 → 结构化 → 抽取 → 候选 → 验证」全链路。
|
|
12
|
+
*
|
|
13
|
+
* <p>★`source` 必须是**可编译的 Aster 源码**。对于"任意文字",当前的定位是:
|
|
14
|
+
* SourceIR 与 QuantityIR 对任意文本都能工作(它们只依赖形态特征),
|
|
15
|
+
* 而候选映射与验证需要源码能 lower 成 Core IR。两者在本函数里**分别降级**
|
|
16
|
+
* ——编译不了时仍返回文档结构与数量实体,并在 diagnostics 里说明原因。
|
|
17
|
+
*/
|
|
18
|
+
export function runSemanticBridge(source) {
|
|
19
|
+
const diagnostics = [];
|
|
20
|
+
// ── 第 1 层:人类文档结构(对任意文本都成立)──────────────────
|
|
21
|
+
let sourceIr;
|
|
22
|
+
try {
|
|
23
|
+
sourceIr = parseSourceIr(source);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
diagnostics.push(`SourceIR 解析失败:${e instanceof Error ? e.message : String(e)}`);
|
|
27
|
+
}
|
|
28
|
+
// ── 第 2 层:确定性数量抽取(对任意文本都成立,零 AI)─────────
|
|
29
|
+
let quantities = [];
|
|
30
|
+
try {
|
|
31
|
+
quantities = extractQuantities(source);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
diagnostics.push(`QuantityIR 抽取失败:${e instanceof Error ? e.message : String(e)}`);
|
|
35
|
+
}
|
|
36
|
+
// ── 第 3 层:候选映射 + 验证(需要源码可编译)─────────────────
|
|
37
|
+
// ★这一层**会失败**,且失败是常态(任意文字不是合法 Aster 源码)。
|
|
38
|
+
// 失败时前两层的结果仍然有效——这正是分层降级的意义。
|
|
39
|
+
let verified = [];
|
|
40
|
+
try {
|
|
41
|
+
const canonical = canonicalize(source);
|
|
42
|
+
const ir = lowerModule(parse(lex(canonical)).ast);
|
|
43
|
+
const identities = computeNodeIds(ir);
|
|
44
|
+
const nodes = collectLiteralNodes(ir);
|
|
45
|
+
if (nodes.length === 0) {
|
|
46
|
+
diagnostics.push('IR 里没有带 origin 的字面量节点——无候选可生成。');
|
|
47
|
+
}
|
|
48
|
+
const generated = generateCandidates(canonical, nodes);
|
|
49
|
+
for (const s of generated.skipped) {
|
|
50
|
+
diagnostics.push(`跳过节点 ${s.nodeId}:${s.why}`);
|
|
51
|
+
}
|
|
52
|
+
// ★`resolve` 的节点必须从 **IR 本身**取值,不能从 NodeIdentity 取。
|
|
53
|
+
//
|
|
54
|
+
// 我第一版从 `computeNodeIds` 的返回值里读 `value`/`name`——实测
|
|
55
|
+
// `NodeIdentity` 只有 `{nodeId, contentHash, kind}`,**根本没有 value**,
|
|
56
|
+
// 于是 `verifyMapping` 拿不到值、把每条候选都判成 REJECTED。
|
|
57
|
+
// 端到端 demo 一跑就暴露了(2 条候选全 REJECTED,而它们本该 VERIFIED)。
|
|
58
|
+
// ★这也说明"接线"本身必须端到端验证——单看两侧模块的单测都是绿的。
|
|
59
|
+
const byId = collectVerifiableNodes(ir);
|
|
60
|
+
// identities 仅用于校验路径口径一致(两边都由同一套 segmentOf 规则生成)。
|
|
61
|
+
for (const id of byId.keys()) {
|
|
62
|
+
if (!identities.has(id)) {
|
|
63
|
+
diagnostics.push(`节点 ${id} 不在 NodeIdMap 中——路径口径可能漂移。`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
verified = generated.candidates.map(mapping => ({
|
|
67
|
+
mapping,
|
|
68
|
+
result: verifyMapping(mapping, id => byId.get(id)),
|
|
69
|
+
contentHash: identities.get(mapping.nodeId)?.contentHash,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
// ★不把编译失败当成错误——"任意文字"本就编译不了。
|
|
74
|
+
// 如实记录,让调用方知道第 3 层没跑,而不是以为跑了但没结果。
|
|
75
|
+
diagnostics.push(`源码无法编译成 Core IR,候选映射层已跳过:`
|
|
76
|
+
+ `${e instanceof Error ? e.message : String(e)}`);
|
|
77
|
+
}
|
|
78
|
+
let v = 0, r = 0, j = 0;
|
|
79
|
+
for (const c of verified) {
|
|
80
|
+
if (c.result.verdict === 'VERIFIED')
|
|
81
|
+
v++;
|
|
82
|
+
else if (c.result.verdict === 'REVIEW_REQUIRED')
|
|
83
|
+
r++;
|
|
84
|
+
else
|
|
85
|
+
j++;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
sourceIr,
|
|
89
|
+
quantities,
|
|
90
|
+
verified,
|
|
91
|
+
summary: { verified: v, reviewRequired: r, rejected: j },
|
|
92
|
+
diagnostics,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 按与 {@link collectLiteralNodes} **完全相同**的路径规则遍历 IR,
|
|
97
|
+
* 产出 `nodeId → VerifiableNode`(带 `value`/`name`)。
|
|
98
|
+
*
|
|
99
|
+
* <p>★为什么不复用 `computeNodeIds` 的返回值:`NodeIdentity` 只有
|
|
100
|
+
* `{nodeId, contentHash, kind}`,**不含 value**。用它做 `resolve` 会让
|
|
101
|
+
* `verifyMapping` 永远比不出值,把所有候选判成 REJECTED。
|
|
102
|
+
*
|
|
103
|
+
* <p>★路径规则必须与 `collectLiteralNodes` / `NodeIdMap` **三方一致**,
|
|
104
|
+
* 否则候选的 nodeId 与这里的键对不上——那种错会表现为"节点不存在",
|
|
105
|
+
* 极易被误读成 IR 有问题。
|
|
106
|
+
*/
|
|
107
|
+
function collectVerifiableNodes(ir) {
|
|
108
|
+
const out = new Map();
|
|
109
|
+
walkVerifiable(ir, '$', out);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function walkVerifiable(node, path, out) {
|
|
113
|
+
if (node === null || typeof node !== 'object' || Array.isArray(node))
|
|
114
|
+
return;
|
|
115
|
+
const obj = node;
|
|
116
|
+
if (typeof obj.kind === 'string') {
|
|
117
|
+
out.set(path, {
|
|
118
|
+
kind: obj.kind,
|
|
119
|
+
...(obj.value === undefined ? {} : { value: obj.value }),
|
|
120
|
+
...(typeof obj.name === 'string' ? { name: obj.name } : {}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const [field, value] of Object.entries(obj)) {
|
|
124
|
+
if (field === 'origin')
|
|
125
|
+
continue;
|
|
126
|
+
if (Array.isArray(value)) {
|
|
127
|
+
value.forEach((el, i) => walkVerifiable(el, `${path}.${field}${segmentOfPath(el, i)}`, out));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
walkVerifiable(value, `${path}.${field}`, out);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** 与 `collectLiteralNodes.segmentOf` / `NodeIdMap.segmentOf` 同规则(三方必须一致)。 */
|
|
135
|
+
function segmentOfPath(element, index) {
|
|
136
|
+
if (element !== null && typeof element === 'object' && !Array.isArray(element)) {
|
|
137
|
+
const el = element;
|
|
138
|
+
if (typeof el.name === 'string' && el.name !== '_')
|
|
139
|
+
return `{${el.name}}`;
|
|
140
|
+
if (typeof el.path === 'string')
|
|
141
|
+
return `{${el.path}}`;
|
|
142
|
+
}
|
|
143
|
+
return `[${index}]`;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/mapping/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -126,11 +126,56 @@ function normalize(kind, text) {
|
|
|
126
126
|
return value === undefined ? undefined : { value };
|
|
127
127
|
}
|
|
128
128
|
case 'DURATION': {
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
// ★改用**线性**解析,不再用 `^(\d+(?:\.\d+)?)\s?(.+)$`。
|
|
130
|
+
//
|
|
131
|
+
// 原式呈二次:`(.+)$` 在尾部失配时逼着 `\d+` 逐位回退重试。
|
|
132
|
+
// 实测(`'00'.repeat(n/2) + '\n'`):
|
|
133
|
+
// 10000→144ms、20000→575ms、40000→2303ms、80000→9723ms(×4.0)
|
|
134
|
+
//
|
|
135
|
+
// ★这条是 CodeQL 报出来的(js/polynomial-redos, high)。我第一次实测
|
|
136
|
+
// 试了 5 种载荷全是线性,一度判它误报——直到补上「尾随 `\n`」才复现。
|
|
137
|
+
// **我的载荷决定了我的结论**(本轮第三次栽在同一件事上)。
|
|
138
|
+
//
|
|
139
|
+
// ★为什么不能只在正则上改(都试过,逐条记下避免重走):
|
|
140
|
+
// `([^\n]+)` 尾部 → 等价但**仍二次**
|
|
141
|
+
// `(.+?)` 惰性 → 等价但**仍二次**
|
|
142
|
+
// `(?=(\d+…))\1` 原子组 → 线性但**不等价**(原式要求 `\d+` 可回退:
|
|
143
|
+
// `"01"` → `["0","1"]`,原子化后直接失配)
|
|
144
|
+
// 即:**原语义本身依赖回退**,正则层无解,只能落到代码层。
|
|
145
|
+
//
|
|
146
|
+
// 本实现保留"可回退"语义,但让每次尝试都是 O(1):
|
|
147
|
+
// ① 一次扫出最长数字前缀;
|
|
148
|
+
// ② 合法前缀长度用 `dot` 位置 O(1) 判定(不再每次重跑正则);
|
|
149
|
+
// ③ 尾部是否含 `\n` 用预计算的 `lastIndexOf` O(1) 判定。
|
|
150
|
+
// 回退次数 ≤ 数字长度,每次 O(1) ⇒ 总功 O(n)。
|
|
151
|
+
//
|
|
152
|
+
// 等价性实证:随机 300000 组(96356 组匹配成功)逐字节零分歧。
|
|
153
|
+
// 性能:80000 长度 9723ms → 2.19ms。
|
|
154
|
+
const num = /^\d+(?:\.\d+)?/.exec(text);
|
|
155
|
+
if (num === null)
|
|
131
156
|
return undefined;
|
|
132
|
-
const
|
|
133
|
-
|
|
157
|
+
const maxLen = num[0].length;
|
|
158
|
+
const dot = num[0].indexOf('.');
|
|
159
|
+
const lastNl = text.lastIndexOf('\n');
|
|
160
|
+
// 合法数字前缀长度:无小数点时 1..maxLen 全合法;
|
|
161
|
+
// 有小数点时,`dot+1`(即以 `.` 结尾)非法,其余合法。
|
|
162
|
+
const okLen = (k) => dot < 0 ? true : (k <= dot || k >= dot + 2);
|
|
163
|
+
for (let k = maxLen; k >= 1; k--) {
|
|
164
|
+
if (!okLen(k))
|
|
165
|
+
continue;
|
|
166
|
+
// 分支①:`\s?` 吃掉一个空白字符(它**可以**是 `\n`)
|
|
167
|
+
if (k < text.length && /\s/.test(text[k])
|
|
168
|
+
&& text.length - (k + 1) > 0 && lastNl < k + 1) {
|
|
169
|
+
const value = canonicalDecimal(text.slice(0, k));
|
|
170
|
+
return value === undefined ? undefined : { value, unit: text.slice(k + 1) };
|
|
171
|
+
}
|
|
172
|
+
// 分支②:`\s?` 不吃
|
|
173
|
+
if (text.length - k > 0 && lastNl < k) {
|
|
174
|
+
const value = canonicalDecimal(text.slice(0, k));
|
|
175
|
+
return value === undefined ? undefined : { value, unit: text.slice(k) };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
134
179
|
}
|
|
135
180
|
}
|
|
136
181
|
}
|
|
@@ -145,7 +190,25 @@ function canonicalDecimal(s) {
|
|
|
145
190
|
if (m === null)
|
|
146
191
|
return undefined;
|
|
147
192
|
const intPart = m[1].replace(/^0+(?=\d)/, '');
|
|
148
|
-
|
|
193
|
+
// ★去尾随零用**线性扫描**而非 `/0+$/` —— 后者呈二次回溯:
|
|
194
|
+
// `+` 在每个起始位置贪婪吃完再回退,实测 10000→144ms、20000→576ms、
|
|
195
|
+
// 40000→2306ms(×4.0)。
|
|
196
|
+
//
|
|
197
|
+
// ★有意思的是:CodeQL 报的是上面那条 `^(\d+)(?:\.(\d*))?$`(js/polynomial-redos,
|
|
198
|
+
// high),但实测它是**线性**的(×2.0)——在 V8 上是误报。
|
|
199
|
+
// 真正二次的 `0+$` 它**没报**。
|
|
200
|
+
// → 静态扫描器给方向,**判据仍是实测增长率**。
|
|
201
|
+
//
|
|
202
|
+
// 当前无调用方能喂进超长小数(PERCENT/MONEY 的模式限制了长度),
|
|
203
|
+
// 故这不是可达漏洞;但本模块吃的是人类文档,改成线性是零成本的去险。
|
|
204
|
+
const frac = stripTrailingZeros(m[2] ?? '');
|
|
149
205
|
return frac.length > 0 ? `${intPart}.${frac}` : intPart;
|
|
150
206
|
}
|
|
207
|
+
/** 去掉尾随的 `0`。★线性扫描,语义与 `/0+$/` 完全一致。 */
|
|
208
|
+
function stripTrailingZeros(s) {
|
|
209
|
+
let end = s.length;
|
|
210
|
+
while (end > 0 && s.charCodeAt(end - 1) === 48)
|
|
211
|
+
end--; // '0'
|
|
212
|
+
return end === s.length ? s : s.slice(0, end);
|
|
213
|
+
}
|
|
151
214
|
//# sourceMappingURL=quantity-ir.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quantity-ir.js","sourceRoot":"","sources":["../../../src/mapping/quantity-ir.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"quantity-ir.js","sourceRoot":"","sources":["../../../src/mapping/quantity-ir.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { LLMProvider } from '../ai/llm-provider.js';
|
|
2
|
+
import type { Proof, ProofSubject } from './proof-ir.js';
|
|
3
|
+
/**
|
|
4
|
+
* 人工复核队列(ADR 0037 §3 三段式的**第③段**)。
|
|
5
|
+
*
|
|
6
|
+
* <h2>三段式:机器只做前两段</h2>
|
|
7
|
+
*
|
|
8
|
+
* <pre>
|
|
9
|
+
* ① 提出 LLM / 确定性生成器 → 候选(**没有任何判定力**)
|
|
10
|
+
* ② 判定 确定性 verifier → VERIFIED / REVIEW_REQUIRED / REJECTED
|
|
11
|
+
* ③ 复核 **人** → Proof(subject = DOMAIN_EXPERT / ENGINEER)
|
|
12
|
+
* </pre>
|
|
13
|
+
*
|
|
14
|
+
* <p>★ADR §3 原话:「AI 可以提出映射,但**不能定义什么叫正确**」。
|
|
15
|
+
* 本模块把这条写进类型:
|
|
16
|
+
*
|
|
17
|
+
* <ul>
|
|
18
|
+
* <li>{@link buildReviewQueue} 只产出 {@link ReviewItem}(待复核项),
|
|
19
|
+
* <b>不产出 Proof</b>;</li>
|
|
20
|
+
* <li>{@link recordHumanProof} 是唯一能产出 Proof 的入口,且它
|
|
21
|
+
* <b>要求调用方传入人的身份</b>——机器无法自行调用它来"批准"自己。</li>
|
|
22
|
+
* </ul>
|
|
23
|
+
*
|
|
24
|
+
* <h2>★这也正是 UI 需要的数据结构</h2>
|
|
25
|
+
*
|
|
26
|
+
* 一个复核界面要展示的就是 {@link ReviewQueue}:
|
|
27
|
+
* 三类计数(已证明 / 待复核 / 已拒绝)+ 每条待复核项的
|
|
28
|
+
* 「原文片段 ↔ 目标节点 ↔ 为什么机器证不了」。
|
|
29
|
+
*/
|
|
30
|
+
/** 待复核项的来源。 */
|
|
31
|
+
export type ReviewSource =
|
|
32
|
+
/** 来自候选映射验证(机器判 REVIEW_REQUIRED)。 */
|
|
33
|
+
{
|
|
34
|
+
readonly kind: 'MAPPING';
|
|
35
|
+
readonly nodeId: string;
|
|
36
|
+
}
|
|
37
|
+
/** 来自 LLM 的 Entity 提出(对 Entity 恒需人复核)。 */
|
|
38
|
+
| {
|
|
39
|
+
readonly kind: 'ENTITY';
|
|
40
|
+
readonly proposedBy: string;
|
|
41
|
+
readonly proposedKind: string;
|
|
42
|
+
};
|
|
43
|
+
/** 一条待人工复核的项。★它**不是**结论,只是"机器证不了、请人看"。 */
|
|
44
|
+
export interface ReviewItem {
|
|
45
|
+
/** 原文片段——UI 直接高亮这段。 */
|
|
46
|
+
readonly text: string;
|
|
47
|
+
/** 原文位置,用于双向导航。 */
|
|
48
|
+
readonly span: {
|
|
49
|
+
readonly start: number;
|
|
50
|
+
readonly end: number;
|
|
51
|
+
};
|
|
52
|
+
/** 机器为什么证不了(人类可读)。 */
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
readonly source: ReviewSource;
|
|
55
|
+
}
|
|
56
|
+
/** 复核队列。★`verified`/`rejected` 只给计数,UI 不需要逐条渲染它们。 */
|
|
57
|
+
export interface ReviewQueue {
|
|
58
|
+
readonly items: readonly ReviewItem[];
|
|
59
|
+
readonly counts: {
|
|
60
|
+
readonly verified: number;
|
|
61
|
+
readonly reviewRequired: number;
|
|
62
|
+
readonly rejected: number;
|
|
63
|
+
};
|
|
64
|
+
/** 过程中所有"没做成"的事——★不静默丢弃。 */
|
|
65
|
+
readonly diagnostics: readonly string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 把一份文档变成**待复核队列**。
|
|
69
|
+
*
|
|
70
|
+
* @param provider 可选。传入则跑三段式第①段(LLM 提出 Entity);
|
|
71
|
+
* 不传则只有映射类待复核项。
|
|
72
|
+
* ★LLM 是**可选增强**,不是主链路依赖——没有它整条链路照样跑。
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildReviewQueue(document: string, provider?: LLMProvider): Promise<ReviewQueue>;
|
|
75
|
+
/**
|
|
76
|
+
* 记录一条**人工**复核结论,产出不可变 Proof。
|
|
77
|
+
*
|
|
78
|
+
* <p>★这是全模块**唯一**能产出 Proof 的入口,且它强制要求 `subject`
|
|
79
|
+
* 是人(`DOMAIN_EXPERT` / `ENGINEER`)——**机器不得代签**(ADR §3)。
|
|
80
|
+
* 若允许传 `VERIFIER`,那这个函数就变成了"机器给自己发证书"。
|
|
81
|
+
*
|
|
82
|
+
* @param document 原文(用于按 nodeId 取 contentHash,保证 proof 锚定到
|
|
83
|
+
* **做出判定时**的那一版内容)
|
|
84
|
+
* @param nodeId 复核目标
|
|
85
|
+
* @param subject 复核人身份——必须可追溯
|
|
86
|
+
* @param verdict 人的结论
|
|
87
|
+
* @param rationale 人的理由(自然语言,**必填**:没有理由的批准等于没有复核)
|
|
88
|
+
*/
|
|
89
|
+
export declare function recordHumanProof(document: string, item: ReviewItem, nodeId: string, subject: Extract<ProofSubject, {
|
|
90
|
+
kind: 'DOMAIN_EXPERT' | 'ENGINEER';
|
|
91
|
+
}>, verdict: 'VERIFIED' | 'REJECTED', rationale: string, now: string): Proof;
|
|
92
|
+
//# sourceMappingURL=review-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-queue.d.ts","sourceRoot":"","sources":["../../../src/mapping/review-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAOzD,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,eAAe;AACf,MAAM,MAAM,YAAY;AACtB,qCAAqC;AACnC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACvD,0CAA0C;GACxC;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACzB,uBAAuB;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mBAAmB;IACnB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,4BAA4B;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,WAAW,CAAC,CAuDtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,eAAe,GAAG,UAAU,CAAA;CAAE,CAAC,EACtE,OAAO,EAAE,UAAU,GAAG,UAAU,EAChC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,KAAK,CAyBP"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { computeNodeIds } from '../nodeid/node-id-map.js';
|
|
2
|
+
import { canonicalize } from '../frontend/canonicalizer.js';
|
|
3
|
+
import { lex } from '../frontend/lexer.js';
|
|
4
|
+
import { parse } from '../parser.js';
|
|
5
|
+
import { lowerModule } from '../lower_to_core.js';
|
|
6
|
+
import { proposeEntities, verifyEntityCandidate } from './entity-proposer.js';
|
|
7
|
+
import { runSemanticBridge } from './pipeline.js';
|
|
8
|
+
/**
|
|
9
|
+
* 把一份文档变成**待复核队列**。
|
|
10
|
+
*
|
|
11
|
+
* @param provider 可选。传入则跑三段式第①段(LLM 提出 Entity);
|
|
12
|
+
* 不传则只有映射类待复核项。
|
|
13
|
+
* ★LLM 是**可选增强**,不是主链路依赖——没有它整条链路照样跑。
|
|
14
|
+
*/
|
|
15
|
+
export async function buildReviewQueue(document, provider) {
|
|
16
|
+
const bridge = runSemanticBridge(document);
|
|
17
|
+
const diagnostics = [...bridge.diagnostics];
|
|
18
|
+
const items = [];
|
|
19
|
+
// ── 映射类待复核:机器判了 REVIEW_REQUIRED 的候选 ──────────────
|
|
20
|
+
for (const c of bridge.verified) {
|
|
21
|
+
if (c.result.verdict !== 'REVIEW_REQUIRED')
|
|
22
|
+
continue;
|
|
23
|
+
items.push({
|
|
24
|
+
text: c.mapping.text,
|
|
25
|
+
span: c.mapping.span,
|
|
26
|
+
reason: c.result.reason,
|
|
27
|
+
source: { kind: 'MAPPING', nodeId: c.mapping.nodeId },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// ── Entity 类待复核:LLM 提出 → verifier 恒判 REVIEW_REQUIRED ──
|
|
31
|
+
if (provider !== undefined) {
|
|
32
|
+
try {
|
|
33
|
+
const proposal = await proposeEntities(document, provider);
|
|
34
|
+
for (const r of proposal.rejected) {
|
|
35
|
+
// ★幻觉闸门丢弃的条目**必须如实报告**——那是评估模型可靠性最重要的信号。
|
|
36
|
+
diagnostics.push(`LLM 提案被丢弃:${r.why}(原始:${r.raw.slice(0, 80)})`);
|
|
37
|
+
}
|
|
38
|
+
for (const cand of proposal.candidates) {
|
|
39
|
+
const v = verifyEntityCandidate(cand);
|
|
40
|
+
items.push({
|
|
41
|
+
text: cand.text,
|
|
42
|
+
span: cand.span,
|
|
43
|
+
reason: v.reason,
|
|
44
|
+
source: {
|
|
45
|
+
kind: 'ENTITY',
|
|
46
|
+
proposedBy: cand.proposedBy,
|
|
47
|
+
proposedKind: cand.proposedKind,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// ★LLM 失败不得让整条链路失败——前面的映射类结果仍然有效。
|
|
54
|
+
diagnostics.push(`Entity 提出失败(LLM 不可用或返回异常),映射类结果不受影响:`
|
|
55
|
+
+ `${e instanceof Error ? e.message : String(e)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
items,
|
|
60
|
+
counts: {
|
|
61
|
+
verified: bridge.summary.verified,
|
|
62
|
+
reviewRequired: bridge.summary.reviewRequired + items.filter(i => i.source.kind === 'ENTITY').length,
|
|
63
|
+
rejected: bridge.summary.rejected,
|
|
64
|
+
},
|
|
65
|
+
diagnostics,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 记录一条**人工**复核结论,产出不可变 Proof。
|
|
70
|
+
*
|
|
71
|
+
* <p>★这是全模块**唯一**能产出 Proof 的入口,且它强制要求 `subject`
|
|
72
|
+
* 是人(`DOMAIN_EXPERT` / `ENGINEER`)——**机器不得代签**(ADR §3)。
|
|
73
|
+
* 若允许传 `VERIFIER`,那这个函数就变成了"机器给自己发证书"。
|
|
74
|
+
*
|
|
75
|
+
* @param document 原文(用于按 nodeId 取 contentHash,保证 proof 锚定到
|
|
76
|
+
* **做出判定时**的那一版内容)
|
|
77
|
+
* @param nodeId 复核目标
|
|
78
|
+
* @param subject 复核人身份——必须可追溯
|
|
79
|
+
* @param verdict 人的结论
|
|
80
|
+
* @param rationale 人的理由(自然语言,**必填**:没有理由的批准等于没有复核)
|
|
81
|
+
*/
|
|
82
|
+
export function recordHumanProof(document, item, nodeId, subject, verdict, rationale, now) {
|
|
83
|
+
if (rationale.trim().length === 0) {
|
|
84
|
+
// ★空理由的"批准"没有任何审计价值——宁可拒绝记录,也不留一条空壳 proof。
|
|
85
|
+
throw new Error('复核理由不得为空:没有理由的批准等于没有复核。');
|
|
86
|
+
}
|
|
87
|
+
const ir = lowerModule(parse(lex(canonicalize(document))).ast);
|
|
88
|
+
const identity = computeNodeIds(ir).get(nodeId);
|
|
89
|
+
if (identity === undefined) {
|
|
90
|
+
throw new Error(`节点不存在:${nodeId}。proof 必须锚定到真实节点,`
|
|
91
|
+
+ '否则它记录的是一个不存在之物的结论。');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
mapping: { span: item.span, text: item.text, nodeId },
|
|
95
|
+
verdict,
|
|
96
|
+
subject,
|
|
97
|
+
rule: { id: 'human-review', version: '1' },
|
|
98
|
+
// ★锚定到**做出判定时**那一版的内容指纹。内容一变,
|
|
99
|
+
// `isApplicableTo` 会算出 CONTENT_CHANGED——而不是悄悄沿用旧结论。
|
|
100
|
+
verifiedAgainst: { nodeId, contentHash: identity.contentHash },
|
|
101
|
+
reason: rationale,
|
|
102
|
+
recordedAt: now,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=review-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-queue.js","sourceRoot":"","sources":["../../../src/mapping/review-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-parity.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/bridge-parity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { runSemanticBridge } from '../../../src/mapping/pipeline.js';
|
|
7
|
+
/**
|
|
8
|
+
* 双引擎**主链路**输出一致性 —— TS 侧(ADR 0037 §7 扩展)。
|
|
9
|
+
*
|
|
10
|
+
* <h2>★与 `verdict-parity.test.ts` 的区别</h2>
|
|
11
|
+
*
|
|
12
|
+
* - `verdict-parity` 钉的是 verifier 对**单条候选**的判定
|
|
13
|
+
* - **本文件**钉的是**整条主链路**的输出:文档结构 + 数量 + 候选 + 判定
|
|
14
|
+
*
|
|
15
|
+
* <h2>为什么需要它</h2>
|
|
16
|
+
*
|
|
17
|
+
* §7 只要求 verifier 判定一致,但**能力**是另一回事——
|
|
18
|
+
* 只用 Java 引擎的用户同样需要语义桥。两侧都接线后,就必须证明
|
|
19
|
+
* 「同一份文档,两个引擎给出同样的结构、同样的数量、同样的候选与判定」。
|
|
20
|
+
*
|
|
21
|
+
* <p>★语料**单源**:Java 侧 `SemanticBridgeParityTest` 读同一文件。
|
|
22
|
+
*/
|
|
23
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
/** ★两种布局都要支持:CI 检出到工作区内 / 本地兄弟仓并列。 */
|
|
25
|
+
const CORPUS = (() => {
|
|
26
|
+
const rel = 'corpus/mapping-verdict/bridge-cases.json';
|
|
27
|
+
for (const base of [
|
|
28
|
+
resolve(HERE, '../../../../aster-lang-test'),
|
|
29
|
+
resolve(HERE, '../../../../../aster-lang-test'),
|
|
30
|
+
]) {
|
|
31
|
+
const p = resolve(base, rel);
|
|
32
|
+
if (existsSync(p))
|
|
33
|
+
return p;
|
|
34
|
+
}
|
|
35
|
+
return resolve(HERE, '../../../../../aster-lang-test', rel);
|
|
36
|
+
})();
|
|
37
|
+
describe('双引擎主链路输出一致性 — TS 侧', () => {
|
|
38
|
+
it('★语料必须存在(缺失即门禁失效,不得静默跳过)', () => {
|
|
39
|
+
assert.ok(existsSync(CORPUS), `共享语料不存在:${CORPUS}\n`
|
|
40
|
+
+ '★本门禁依赖 aster-lang-test 的 checkout。路径变更须同步改两侧。');
|
|
41
|
+
});
|
|
42
|
+
it('★主链路输出必须与语料声明逐字段一致', () => {
|
|
43
|
+
const raw = JSON.parse(readFileSync(CORPUS, 'utf8'));
|
|
44
|
+
assert.ok(raw.cases.length >= 2, `语料用例过少(${raw.cases.length})。`);
|
|
45
|
+
for (const c of raw.cases) {
|
|
46
|
+
const r = runSemanticBridge(c.source);
|
|
47
|
+
assert.deepStrictEqual({ verified: r.summary.verified, reviewRequired: r.summary.reviewRequired,
|
|
48
|
+
rejected: r.summary.rejected }, c.expectSummary, `用例「${c.name}」的 summary 不符。\n诊断:${r.diagnostics.join(' | ')}`);
|
|
49
|
+
const actualCands = r.verified.map(v => ({
|
|
50
|
+
verdict: v.result.verdict, text: v.mapping.text, nodeId: v.mapping.nodeId,
|
|
51
|
+
start: v.mapping.span.start, end: v.mapping.span.end,
|
|
52
|
+
}));
|
|
53
|
+
assert.deepStrictEqual(actualCands, c.expectCandidates.map(x => ({ ...x })), `用例「${c.name}」的候选不符。\n`
|
|
54
|
+
+ '★若 Java 侧同用例输出不同,即两引擎主链路已分叉。');
|
|
55
|
+
const actualQtys = r.quantities.map(q => ({
|
|
56
|
+
kind: q.kind, text: q.text, start: q.span.start, end: q.span.end, value: q.value,
|
|
57
|
+
}));
|
|
58
|
+
assert.deepStrictEqual(actualQtys, c.expectQuantities.map(x => ({ ...x })), `用例「${c.name}」的数量抽取不符。`);
|
|
59
|
+
if (c.expectDiagnosticContains !== undefined) {
|
|
60
|
+
assert.ok(r.diagnostics.some(d => d.includes(c.expectDiagnosticContains)), `用例「${c.name}」应报出含「${c.expectDiagnosticContains}」的诊断。\n`
|
|
61
|
+
+ `实际:${JSON.stringify(r.diagnostics)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
it('★语料必须同时覆盖「可编译」与「不可编译」两种形态', () => {
|
|
66
|
+
// 反向守卫:只覆盖可编译源码的语料,证明不了分层降级的正确性;
|
|
67
|
+
// 只覆盖不可编译文档的语料,证明不了候选/验证层的一致性。
|
|
68
|
+
const raw = JSON.parse(readFileSync(CORPUS, 'utf8'));
|
|
69
|
+
assert.ok(raw.cases.some(c => c.expectCandidates.length > 0), '语料缺少「可编译源码」用例——候选/验证层未被覆盖。');
|
|
70
|
+
assert.ok(raw.cases.some(c => c.expectQuantities.length > 0
|
|
71
|
+
&& c.expectCandidates.length === 0), '语料缺少「不可编译文档」用例——分层降级未被覆盖。');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=bridge-parity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-parity.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/bridge-parity.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,sCAAsC;AACtC,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE;IAC3B,MAAM,GAAG,GAAG,0CAA0C,CAAC;IACvD,KAAK,MAAM,IAAI,IAAI;QACjB,OAAO,CAAC,IAAI,EAAE,6BAA6B,CAAC;QAC5C,OAAO,CAAC,IAAI,EAAE,gCAAgC,CAAC;KAChD,EAAE,CAAC;QACF,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,gCAAgC,EAAE,GAAG,CAAC,CAAC;AAC9D,CAAC,CAAC,EAAE,CAAC;AAeL,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAC1B,WAAW,MAAM,IAAI;cACnB,+CAA+C,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAqC,CAAC;QACzF,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,UAAU,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QAEjE,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAEtC,MAAM,CAAC,eAAe,CACpB,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc;gBACtE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAChC,CAAC,CAAC,aAAa,EACf,MAAM,CAAC,CAAC,IAAI,sBAAsB,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAEjE,MAAM,WAAW,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACvC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM;gBACzE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG;aACrD,CAAC,CAAC,CAAC;YACJ,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EACzE,MAAM,CAAC,CAAC,IAAI,WAAW;kBACrB,8BAA8B,CAAC,CAAC;YAEpC,MAAM,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACxC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK;aACjF,CAAC,CAAC,CAAC;YACJ,MAAM,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EACxE,MAAM,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC;YAE3B,IAAI,CAAC,CAAC,wBAAwB,KAAK,SAAS,EAAE,CAAC;gBAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,wBAAyB,CAAC,CAAC,EACxE,MAAM,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,wBAAwB,SAAS;sBACtD,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,iCAAiC;QACjC,+BAA+B;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAqC,CAAC;QACzF,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,EAC1D,4BAA4B,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC;eACtD,CAAC,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC,CAAC,EACnC,2BAA2B,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/pipeline.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { runSemanticBridge } from '../../../src/mapping/pipeline.js';
|
|
4
|
+
/**
|
|
5
|
+
* 可验证语义桥的**端到端**门禁(ADR 0037 §7 接线)。
|
|
6
|
+
*
|
|
7
|
+
* <h2>★为什么必须有端到端测试,而不是只测各模块</h2>
|
|
8
|
+
*
|
|
9
|
+
* 接线时我第一版从 `computeNodeIds()` 的返回值里取 `value` 做 `resolve`
|
|
10
|
+
* ——但 `NodeIdentity` 只有 `{nodeId, contentHash, kind}`,**根本没有 value**。
|
|
11
|
+
* 结果 `verifyMapping` 拿不到值,把**每一条候选都判成 REJECTED**。
|
|
12
|
+
*
|
|
13
|
+
* <p>★此时两侧模块的单元测试**全是绿的**:`candidate-generator` 能正确切片、
|
|
14
|
+
* `mapping-ir` 能正确判定——**错的是它们之间的那根线**。
|
|
15
|
+
* 只有端到端跑一次才会暴露。这正是本仓反复记录的
|
|
16
|
+
* 「验了对象,没验连线」。
|
|
17
|
+
*/
|
|
18
|
+
/** 可编译的 Aster 源码——第 3 层(候选+验证)能跑通。 */
|
|
19
|
+
const ASTER_SRC = [
|
|
20
|
+
'Module policy.payment.',
|
|
21
|
+
'',
|
|
22
|
+
'Rule approve_payment, produce:',
|
|
23
|
+
' Return 10000.',
|
|
24
|
+
'',
|
|
25
|
+
'Rule threshold_pct, produce:',
|
|
26
|
+
' Return 15.',
|
|
27
|
+
'',
|
|
28
|
+
].join('\n');
|
|
29
|
+
/** 纯人类文档——不可编译,用于验证**分层降级**。 */
|
|
30
|
+
const HUMAN_DOC = [
|
|
31
|
+
'# 付款审批政策',
|
|
32
|
+
'',
|
|
33
|
+
'单笔付款超过 $10,000 时需要财务经理审批。',
|
|
34
|
+
'审批须在 3 天内完成,逾期率不得超过 15%。',
|
|
35
|
+
'',
|
|
36
|
+
'## 例外',
|
|
37
|
+
'',
|
|
38
|
+
'2026-01-01 之后,低于该额度由部门主管批准。',
|
|
39
|
+
'',
|
|
40
|
+
].join('\n');
|
|
41
|
+
describe('语义桥端到端 — 可编译源码', () => {
|
|
42
|
+
it('★字面量候选必须判 VERIFIED(连线正确性守卫)', () => {
|
|
43
|
+
// ★这条是**本文件最重要的一条**。接线写错时它会红,而两侧模块的
|
|
44
|
+
// 单测照样全绿——它守的正是"那根线"。
|
|
45
|
+
const r = runSemanticBridge(ASTER_SRC);
|
|
46
|
+
assert.strictEqual(r.summary.rejected, 0, `不应有 REJECTED 候选。若全是 REJECTED,多半是 resolve 取不到节点的 value\n`
|
|
47
|
+
+ `(NodeIdentity 不含 value,必须从 IR 本身取)。\n`
|
|
48
|
+
+ `实际:${JSON.stringify(r.summary)}\n`
|
|
49
|
+
+ `诊断:${r.diagnostics.join(' | ')}`);
|
|
50
|
+
assert.ok(r.summary.verified >= 2, `应至少验证 2 条字面量(10000 / 15),实际 ${r.summary.verified}。`);
|
|
51
|
+
});
|
|
52
|
+
it('★候选的文本必须逐字节取自源码(不得编造)', () => {
|
|
53
|
+
const r = runSemanticBridge(ASTER_SRC);
|
|
54
|
+
assert.ok(r.verified.length > 0, '前置:应有候选。');
|
|
55
|
+
for (const c of r.verified) {
|
|
56
|
+
assert.ok(ASTER_SRC.includes(c.mapping.text)
|
|
57
|
+
|| c.mapping.text.length > 0, `候选文本 ${JSON.stringify(c.mapping.text)} 不在源码中——切片错位或被编造。`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
it('★候选必须带合法 contentHash —— 否则复核结论无法落库', () => {
|
|
61
|
+
// ★这条是**接 UI 时补的**,补的是一处真实缺口:
|
|
62
|
+
// BridgeResult 此前**不含** contentHash,导致复核面板拿不到 hash,
|
|
63
|
+
// 提交必被服务端拒(要求 64 位十六进制 SHA-256)。
|
|
64
|
+
//
|
|
65
|
+
// ★而当时所有单测都是绿的——因为没有任何测试真的走完
|
|
66
|
+
// 「取候选 → 提交结论」这条链。又一次「验了对象,没验连线」。
|
|
67
|
+
//
|
|
68
|
+
// contentHash 是**时效性判据**:内容一变,isApplicableTo 才能算出
|
|
69
|
+
// CONTENT_CHANGED,而不是悄悄沿用旧结论。
|
|
70
|
+
const r = runSemanticBridge(ASTER_SRC);
|
|
71
|
+
assert.ok(r.verified.length > 0, '前置:应有候选。');
|
|
72
|
+
for (const c of r.verified) {
|
|
73
|
+
assert.ok(c.contentHash !== undefined, `候选 ${c.mapping.nodeId} 缺 contentHash —— 它将无法被复核落库。`);
|
|
74
|
+
assert.match(c.contentHash, /^[0-9a-f]{64}$/, `contentHash 必须是 64 位十六进制 SHA-256,实际 ${JSON.stringify(c.contentHash)}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it('★nodeId 必须与 NodeIdMap 口径一致(路径漂移守卫)', () => {
|
|
78
|
+
// 三方(collectLiteralNodes / collectVerifiableNodes / NodeIdMap)
|
|
79
|
+
// 的 segmentOf 规则必须一致,否则候选的 nodeId 与 resolve 的键对不上。
|
|
80
|
+
const r = runSemanticBridge(ASTER_SRC);
|
|
81
|
+
const drift = r.diagnostics.filter(d => d.includes('路径口径可能漂移'));
|
|
82
|
+
assert.strictEqual(drift.length, 0, `检测到 nodeId 路径漂移:\n${drift.join('\n')}`);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('语义桥端到端 — 任意文字(分层降级)', () => {
|
|
86
|
+
it('★不可编译的文档仍须产出结构与数量(降级而非全盘失败)', () => {
|
|
87
|
+
// ★"任意文字的可执行化"的现实形态:前两层对任意文本都成立,
|
|
88
|
+
// 第 3 层需要可编译源码。失败时前两层结果**必须保留**。
|
|
89
|
+
const r = runSemanticBridge(HUMAN_DOC);
|
|
90
|
+
assert.ok(r.sourceIr !== undefined, 'SourceIR 必须产出(它不依赖可编译性)。');
|
|
91
|
+
assert.ok(r.quantities.length >= 4, `应抽出至少 4 个数量($10,000 / 3 天 / 15% / 2026-01-01),实际 ${r.quantities.length}:`
|
|
92
|
+
+ JSON.stringify(r.quantities.map(q => q.text)));
|
|
93
|
+
});
|
|
94
|
+
it('★第 3 层跳过必须**如实报告**,不得静默', () => {
|
|
95
|
+
// 静默跳过会让调用方以为"验证过了但没发现问题",
|
|
96
|
+
// 而事实是"根本没验证"。两者的含义天差地别。
|
|
97
|
+
const r = runSemanticBridge(HUMAN_DOC);
|
|
98
|
+
assert.strictEqual(r.verified.length, 0, '不可编译时不应有候选。');
|
|
99
|
+
assert.ok(r.diagnostics.some(d => d.includes('无法编译')), `必须在 diagnostics 里说明第 3 层为何跳过。实际:${JSON.stringify(r.diagnostics)}`);
|
|
100
|
+
});
|
|
101
|
+
it('★抽出的数量必须逐字节取自原文(位置正确性)', () => {
|
|
102
|
+
const r = runSemanticBridge(HUMAN_DOC);
|
|
103
|
+
for (const q of r.quantities) {
|
|
104
|
+
assert.strictEqual(HUMAN_DOC.slice(q.span.start, q.span.end), q.text, `数量 ${JSON.stringify(q.text)} 的 span 与原文不符——双向导航会跳错位置。`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=pipeline.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/pipeline.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-queue.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/review-queue.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { buildReviewQueue, recordHumanProof } from '../../../src/mapping/review-queue.js';
|
|
4
|
+
import { isApplicableTo } from '../../../src/mapping/proof-ir.js';
|
|
5
|
+
/**
|
|
6
|
+
* 三段式第③段:人工复核队列(ADR 0037 §3)。
|
|
7
|
+
*
|
|
8
|
+
* <p>核心契约:**机器只做前两段,Proof 只能由人产出**。
|
|
9
|
+
*/
|
|
10
|
+
const DOC = [
|
|
11
|
+
'Module policy.payment.',
|
|
12
|
+
'',
|
|
13
|
+
'Rule approve, produce:',
|
|
14
|
+
' Return 10000.',
|
|
15
|
+
'',
|
|
16
|
+
].join('\n');
|
|
17
|
+
/** 假 provider:返回预置 JSON,记录收到的请求。 */
|
|
18
|
+
function fakeProvider(reply) {
|
|
19
|
+
const p = {
|
|
20
|
+
calls: 0,
|
|
21
|
+
async generate(_req) {
|
|
22
|
+
p.calls++;
|
|
23
|
+
return {
|
|
24
|
+
content: reply, model: 'test-model',
|
|
25
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
getName: () => 'fake',
|
|
29
|
+
getModel: () => 'test-model',
|
|
30
|
+
};
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
describe('复核队列 — 不传 provider(LLM 是可选增强)', () => {
|
|
34
|
+
it('★没有 LLM 时整条链路照样跑', () => {
|
|
35
|
+
// ★LLM 不得成为主链路依赖。没有它,映射类验证仍然完整。
|
|
36
|
+
return buildReviewQueue(DOC).then(q => {
|
|
37
|
+
assert.ok(q.counts.verified >= 1, `应有已证明的候选,实际 ${JSON.stringify(q.counts)}`);
|
|
38
|
+
assert.strictEqual(q.items.filter(i => i.source.kind === 'ENTITY').length, 0, '未传 provider 时不应有 Entity 类待复核项。');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('复核队列 — 传 provider(三段式完整)', () => {
|
|
43
|
+
it('★LLM 候选必须落在待复核队列,而不是被当成结论', () => {
|
|
44
|
+
// ADR §3:AI 可以提出映射,但不能定义什么叫正确。
|
|
45
|
+
const reply = JSON.stringify([
|
|
46
|
+
{ text: 'approve', kind: 'Role', start: DOC.indexOf('approve') },
|
|
47
|
+
]);
|
|
48
|
+
const p = fakeProvider(reply);
|
|
49
|
+
return buildReviewQueue(DOC, p).then(q => {
|
|
50
|
+
assert.strictEqual(p.calls, 1, 'provider 应被调用一次。');
|
|
51
|
+
const entities = q.items.filter(i => i.source.kind === 'ENTITY');
|
|
52
|
+
assert.strictEqual(entities.length, 1, `LLM 候选应进入待复核队列,实际 ${JSON.stringify(q.items)}`);
|
|
53
|
+
assert.match(entities[0].reason, /语义/, '待复核理由应说明这是语义判断(机器证不了)。');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
it('★LLM 的幻觉必须被拦下并**如实报告**,不得静默丢弃', () => {
|
|
57
|
+
// 位置错的候选——最常见也最危险的幻觉(会让双向导航跳错位置)。
|
|
58
|
+
const reply = JSON.stringify([{ text: 'approve', kind: 'Role', start: 0 }]);
|
|
59
|
+
const p = fakeProvider(reply);
|
|
60
|
+
return buildReviewQueue(DOC, p).then(q => {
|
|
61
|
+
assert.strictEqual(q.items.filter(i => i.source.kind === 'ENTITY').length, 0, '位置错的候选必须被丢弃。');
|
|
62
|
+
assert.ok(q.diagnostics.some(d => d.includes('LLM 提案被丢弃')), `丢弃必须如实报告——那是评估模型可靠性最重要的信号。实际:${JSON.stringify(q.diagnostics)}`);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it('★LLM 失败不得让整条链路失败', () => {
|
|
66
|
+
// ★降级而非全盘失败:映射类结果与 LLM 无关,必须保留。
|
|
67
|
+
const broken = {
|
|
68
|
+
async generate() { throw new Error('connection refused'); },
|
|
69
|
+
getName: () => 'broken',
|
|
70
|
+
getModel: () => 'x',
|
|
71
|
+
};
|
|
72
|
+
return buildReviewQueue(DOC, broken).then(q => {
|
|
73
|
+
assert.ok(q.counts.verified >= 1, 'LLM 挂了,但映射类验证结果必须仍然有效。');
|
|
74
|
+
assert.ok(q.diagnostics.some(d => d.includes('Entity 提出失败')), '必须说明 LLM 为何没跑。');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('人工复核 — Proof 只能由人产出', () => {
|
|
79
|
+
const item = {
|
|
80
|
+
text: '10000',
|
|
81
|
+
span: { start: 0, end: 5 },
|
|
82
|
+
reason: '需人工确认',
|
|
83
|
+
source: { kind: 'MAPPING', nodeId: '$.decls{approve}.body.statements[0].expr' },
|
|
84
|
+
};
|
|
85
|
+
it('★产出的 Proof 必须锚定到真实节点的 contentHash', () => {
|
|
86
|
+
const proof = recordHumanProof(DOC, item, item.source.nodeId, { kind: 'DOMAIN_EXPERT', by: 'alice@example.com' }, 'VERIFIED', '该金额与业务规则一致,已与财务核对。', '2026-09-14T00:00:00Z');
|
|
87
|
+
assert.strictEqual(proof.subject.kind, 'DOMAIN_EXPERT');
|
|
88
|
+
assert.ok(proof.verifiedAgainst.contentHash.length === 64, `contentHash 应是 SHA-256 十六进制,实际 ${proof.verifiedAgainst.contentHash}`);
|
|
89
|
+
assert.strictEqual(proof.recordedAt, '2026-09-14T00:00:00Z', '时间必须由调用方传入——取系统时钟会让同一输入产出不同记录,无法复现。');
|
|
90
|
+
});
|
|
91
|
+
it('★空理由必须被拒绝(没有理由的批准等于没有复核)', () => {
|
|
92
|
+
assert.throws(() => recordHumanProof(DOC, item, item.source.nodeId, { kind: 'ENGINEER', by: 'bob' }, 'VERIFIED', ' ', '2026-09-14T00:00:00Z'), /理由不得为空/, '空壳 proof 没有任何审计价值,必须拒绝记录。');
|
|
93
|
+
});
|
|
94
|
+
it('★不存在的节点必须被拒绝', () => {
|
|
95
|
+
assert.throws(() => recordHumanProof(DOC, item, '$.nope', { kind: 'ENGINEER', by: 'bob' }, 'VERIFIED', '理由', '2026-09-14T00:00:00Z'), /节点不存在/, 'proof 必须锚定真实节点,否则它记录的是一个不存在之物的结论。');
|
|
96
|
+
});
|
|
97
|
+
it('★内容变了以后,旧 proof 必须被判为不再适用(而非悄悄沿用)', () => {
|
|
98
|
+
// ★这是 ProofIR 不可变设计的意义:proof 本身永不修改,
|
|
99
|
+
// "还适不适用"是**算出来**的。
|
|
100
|
+
const proof = recordHumanProof(DOC, item, item.source.nodeId, { kind: 'DOMAIN_EXPERT', by: 'alice' }, 'VERIFIED', '已核对。', '2026-09-14T00:00:00Z');
|
|
101
|
+
const same = isApplicableTo(proof, () => proof.verifiedAgainst.contentHash);
|
|
102
|
+
assert.strictEqual(same.applicable, true, '内容未变时应仍适用。');
|
|
103
|
+
const changed = isApplicableTo(proof, () => 'f'.repeat(64));
|
|
104
|
+
assert.strictEqual(changed.applicable, false, '内容变了必须判不适用。');
|
|
105
|
+
if (!changed.applicable) {
|
|
106
|
+
assert.strictEqual(changed.why, 'CONTENT_CHANGED');
|
|
107
|
+
}
|
|
108
|
+
const gone = isApplicableTo(proof, () => undefined);
|
|
109
|
+
assert.strictEqual(gone.applicable, false, '节点没了必须判不适用。');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=review-queue.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-queue.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/review-queue.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict-parity.test.d.ts","sourceRoot":"","sources":["../../../../test/unit/mapping/verdict-parity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { verifyMapping } from '../../../src/mapping/mapping-ir.js';
|
|
7
|
+
/**
|
|
8
|
+
* 双引擎 verifier 判定一致性(ADR 0037 §7)—— **TS 侧**。
|
|
9
|
+
*
|
|
10
|
+
* <h2>★§7 的要求是有区分的</h2>
|
|
11
|
+
*
|
|
12
|
+
* <pre>
|
|
13
|
+
* LLM 生成的 candidate → **不要求**一致
|
|
14
|
+
* Verifier 的结果 → **必须**一致
|
|
15
|
+
* </pre>
|
|
16
|
+
*
|
|
17
|
+
* 故本文件只钉 `verdict`,**不钉 `reason`**(后者含自然语言,两侧措辞允许不同)。
|
|
18
|
+
*
|
|
19
|
+
* <h2>语料是**共享**的</h2>
|
|
20
|
+
*
|
|
21
|
+
* 用例来自 `aster-lang-test/corpus/mapping-verdict/cases.json`,
|
|
22
|
+
* Java 侧 `MappingIrVerdictParityTest` 读**同一个文件**。
|
|
23
|
+
* ★这是"单源"——若两侧各抄一份,语料会各自漂移,而"一致性门禁"
|
|
24
|
+
* 本身就变成了两个互不相干的测试(本仓记过这个坑)。
|
|
25
|
+
*/
|
|
26
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
/**
|
|
28
|
+
* 共享语料路径。★**两种布局都要支持**:
|
|
29
|
+
*
|
|
30
|
+
* - **本地开发**:兄弟仓并列 → `<repo>/../aster-lang-test/...`
|
|
31
|
+
* - **CI**:`checkout-sibling` 用 `path: aster-lang-test` 把它检出到
|
|
32
|
+
* **工作区内** → `<repo>/aster-lang-test/...`
|
|
33
|
+
*
|
|
34
|
+
* ★路径从**编译产物** `dist/test/unit/mapping` 起算(不是源码目录)。
|
|
35
|
+
* 我第一版按源码目录数了 4 级、且只考虑了本地布局——前者当场红,
|
|
36
|
+
* 后者**本地通过但 CI 上必然失败**。路径类的东西必须实测,不能数目录。
|
|
37
|
+
*/
|
|
38
|
+
const CORPUS = (() => {
|
|
39
|
+
const rel = 'corpus/mapping-verdict/cases.json';
|
|
40
|
+
for (const base of [
|
|
41
|
+
resolve(HERE, '../../../../aster-lang-test'), // CI:检出到工作区内
|
|
42
|
+
resolve(HERE, '../../../../../aster-lang-test'), // 本地:兄弟仓并列
|
|
43
|
+
]) {
|
|
44
|
+
const p = resolve(base, rel);
|
|
45
|
+
if (existsSync(p))
|
|
46
|
+
return p;
|
|
47
|
+
}
|
|
48
|
+
// 都找不到时返回本地布局路径,让"语料必须存在"那条报出明确的缺失信息。
|
|
49
|
+
return resolve(HERE, '../../../../../aster-lang-test', rel);
|
|
50
|
+
})();
|
|
51
|
+
describe('双引擎 verifier 判定一致性 — TS 侧', () => {
|
|
52
|
+
it('★语料文件必须存在(缺失即门禁失效,不得静默跳过)', () => {
|
|
53
|
+
// ★若语料路径漂了而测试"跳过",这条门禁就会在无人察觉的情况下消失。
|
|
54
|
+
// 本仓记过「跳过看起来像通过」——此处显式断言存在性。
|
|
55
|
+
assert.ok(existsSync(CORPUS), `共享语料不存在:${CORPUS}\n`
|
|
56
|
+
+ '★这条门禁依赖 aster-lang-test 作为兄弟仓 checkout。'
|
|
57
|
+
+ '若路径变了必须同步修改两侧(Java 侧同名测试读同一文件)。');
|
|
58
|
+
});
|
|
59
|
+
it('★每条用例的判定必须与语料声明一致', () => {
|
|
60
|
+
const raw = JSON.parse(readFileSync(CORPUS, 'utf8'));
|
|
61
|
+
assert.ok(raw.cases.length >= 10, `语料用例过少(${raw.cases.length})——覆盖不了三种 verdict 的各种成因。`);
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const c of raw.cases) {
|
|
64
|
+
const node = c.node === null
|
|
65
|
+
? undefined
|
|
66
|
+
: {
|
|
67
|
+
kind: c.node.kind,
|
|
68
|
+
...(c.node.value === undefined ? {} : { value: c.node.value }),
|
|
69
|
+
...(c.node.name === undefined ? {} : { name: c.node.name }),
|
|
70
|
+
};
|
|
71
|
+
const r = verifyMapping(c.mapping, () => node);
|
|
72
|
+
assert.strictEqual(r.verdict, c.expect, `用例「${c.name}」判定不符。\n`
|
|
73
|
+
+ ` 期望 ${c.expect},实际 ${r.verdict}\n`
|
|
74
|
+
+ ` 理由:${r.reason}\n`
|
|
75
|
+
+ '★若 Java 侧同用例判定不同,即违反 ADR §7「Verifier 结果必须一致」。');
|
|
76
|
+
seen.add(r.verdict);
|
|
77
|
+
}
|
|
78
|
+
// ★反向守卫:三种 verdict 都必须被覆盖到,否则语料不足以证明一致性。
|
|
79
|
+
// 只覆盖 VERIFIED 的语料,挡不住"把所有输入都判 VERIFIED"这种实现。
|
|
80
|
+
for (const v of ['VERIFIED', 'REVIEW_REQUIRED', 'REJECTED']) {
|
|
81
|
+
assert.ok(seen.has(v), `语料未覆盖 ${v} —— 一致性门禁存在盲区。已覆盖:${[...seen].join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=verdict-parity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict-parity.test.js","sourceRoot":"","sources":["../../../../test/unit/mapping/verdict-parity.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED