@principles/core 1.240.21 → 1.241.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-flag.test.d.ts +15 -0
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-flag.test.d.ts.map +1 -0
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-flag.test.js +90 -0
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-flag.test.js.map +1 -0
- package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js +6 -0
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.d.ts +11 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.js +124 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.d.ts +12 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.js +253 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.js +279 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.d.ts +22 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.js +92 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.d.ts +165 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.js +657 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.d.ts +22 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.js +229 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.js +1039 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.d.ts +22 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.js +280 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.d.ts +25 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.js +413 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.d.ts +12 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.js +176 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.d.ts +19 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.js +116 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.js.map +1 -0
- package/dist/runtime-v2/internalization/artifact-summary.d.ts +106 -0
- package/dist/runtime-v2/internalization/artifact-summary.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/artifact-summary.js +331 -0
- package/dist/runtime-v2/internalization/artifact-summary.js.map +1 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +29 -1
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/attach-summary-envelope.d.ts +60 -0
- package/dist/runtime-v2/internalization/attach-summary-envelope.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/attach-summary-envelope.js +126 -0
- package/dist/runtime-v2/internalization/attach-summary-envelope.js.map +1 -0
- package/dist/runtime-v2/internalization/diag-distiller-runner.d.ts +1 -1
- package/dist/runtime-v2/internalization/diag-distiller-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/diag-distiller-runner.js +19 -2
- package/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -1
- package/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/diag-router-runner.d.ts +1 -1
- package/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/diag-router-runner.js +25 -1
- package/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts +9 -1
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.js +28 -6
- package/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +35 -2
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-runner.js +25 -1
- package/dist/runtime-v2/internalization/philosopher-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.js +23 -1
- package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +30 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +106 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/runtime-v2/runner/peer-runner-types.d.ts +12 -0
- package/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -1
- package/dist/runtime-v2/store/commit/diagnostician-committer.d.ts +15 -0
- package/dist/runtime-v2/store/commit/diagnostician-committer.d.ts.map +1 -1
- package/dist/runtime-v2/store/commit/diagnostician-committer.js +19 -1
- package/dist/runtime-v2/store/commit/diagnostician-committer.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 0 Spike fixtures — synthetic baseline chains for the
|
|
3
|
+
* internalization progressive-disclosure value verification.
|
|
4
|
+
*
|
|
5
|
+
* SPIKE-ONLY. This module is deliberately isolated:
|
|
6
|
+
* - it is imported by Spike tests only (task 1.1 self-check, task 1.2 driver);
|
|
7
|
+
* - it is NOT exported from any barrel (`internalization/index.ts` untouched);
|
|
8
|
+
* - it imports **types only**, so it adds no production runtime dependency;
|
|
9
|
+
* - it performs no I/O (core is pure logic — AGENTS.md `antipattern-core-io`).
|
|
10
|
+
*
|
|
11
|
+
* Why the chain starts at the diagnostician chain and not at dreamer
|
|
12
|
+
* (design §4.7.1 / F13): `diag_router` is dreamer's predecessor on the task
|
|
13
|
+
* graph edge, so it is the only data source for the `pain.summary.*` /
|
|
14
|
+
* `diagnosis.summary.*` fields the `pain_to_dreamer` diagnosis segment needs.
|
|
15
|
+
* A chain that started at dreamer could only exercise 2 of the 3 segments.
|
|
16
|
+
*
|
|
17
|
+
* Every `contentJson` shape below is taken from the real output type in the
|
|
18
|
+
* repo — no invented fields:
|
|
19
|
+
* diag_rootcause → `DiagRootCauseOutputV1` (diagnostician/diag-rootcause-output.ts)
|
|
20
|
+
* diag_distiller → `DiagDistillerOutputV1` (diagnostician/diag-distiller-output.ts)
|
|
21
|
+
* diag_router → `DiagnosticianOutputV1` (diagnostician-output.ts)
|
|
22
|
+
* dreamer → `DreamerOutputV1` (dreamer-output.ts)
|
|
23
|
+
* philosopher → `PhilosopherOutputV1` (philosopher-output.ts)
|
|
24
|
+
* scribe → `ScribeOutputV1` (scribe-output.ts)
|
|
25
|
+
* artificer → `ArtificerRuleOutput` (artificer-output.ts)
|
|
26
|
+
* evaluator → `EvaluatorOutputV2` (evaluator-output.ts)
|
|
27
|
+
*
|
|
28
|
+
* The evaluator hop carries only fields that exist today (V1 + optional
|
|
29
|
+
* `codeReview`). `painCoverage` / `compressionFidelity` are Layer 2 additions
|
|
30
|
+
* and are intentionally absent — the expected defect lives in fixture metadata
|
|
31
|
+
* (`expectedDefect`), never inside the LLM-shaped payload, so a Spike assertion
|
|
32
|
+
* cannot trivially read the answer out of the artifact it is judging (ERR-088).
|
|
33
|
+
*
|
|
34
|
+
* @see .kiro/specs/internalization-progressive-disclosure/design.md §12 Phase 0, §16.3
|
|
35
|
+
* @see Requirement 12.2
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Stage order along the synthetic chain. Mirrors `DIAGNOSTICIAN_EDGES`
|
|
39
|
+
* followed by `ALLOWED_EDGES` (design F5 / F14).
|
|
40
|
+
*/
|
|
41
|
+
export const SPIKE_STAGE_ORDER = [
|
|
42
|
+
'diag_rootcause',
|
|
43
|
+
'diag_distiller',
|
|
44
|
+
'diag_router',
|
|
45
|
+
'dreamer',
|
|
46
|
+
'philosopher',
|
|
47
|
+
'scribe',
|
|
48
|
+
'artificer',
|
|
49
|
+
'evaluator',
|
|
50
|
+
];
|
|
51
|
+
/** Ordered hops of a chain. `contentJson` stays `unknown` (rc-1). */
|
|
52
|
+
export function spikeChainHops(chain) {
|
|
53
|
+
return [
|
|
54
|
+
chain.diagRootCause,
|
|
55
|
+
chain.diagDistiller,
|
|
56
|
+
chain.diagRouter,
|
|
57
|
+
chain.dreamer,
|
|
58
|
+
chain.philosopher,
|
|
59
|
+
chain.scribe,
|
|
60
|
+
chain.artificer,
|
|
61
|
+
chain.evaluator,
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
// ── Shared scenario text ─────────────────────────────────────────────────────
|
|
65
|
+
//
|
|
66
|
+
// All three chains share byte-identical diagnostician / dreamer / philosopher
|
|
67
|
+
// content. Only the scribe principle text differs, so any difference the Spike
|
|
68
|
+
// observes is attributable to the dreamer→scribe compression and nothing else.
|
|
69
|
+
const GENERATED_AT = '2026-07-28T00:00:00.000Z';
|
|
70
|
+
/** The three concrete actions dreamer prescribes. Defect chain B loses these. */
|
|
71
|
+
export const CONCRETE_ACTIONS = [
|
|
72
|
+
'审计文件树',
|
|
73
|
+
'grep 全部 imports',
|
|
74
|
+
'检查导出依赖图',
|
|
75
|
+
];
|
|
76
|
+
/** The abstract phrase defect chain B substitutes for the concrete actions. */
|
|
77
|
+
export const ABSTRACTED_PHRASE = '理解架构';
|
|
78
|
+
/** The dreamer risk level. Defect chain A loses this dimension entirely. */
|
|
79
|
+
export const DREAMER_RISK_LEVEL = 'high';
|
|
80
|
+
const ROOT_CAUSE_TEXT = 'Assumption: Agent 假设读过定义文件就等于掌握了该导出符号的影响面,因此跳过了调用方枚举。';
|
|
81
|
+
const EVIDENCE = [
|
|
82
|
+
{ sourceRef: 'run://spike-rename#tsc', note: '重命名后 4 个包 tsc 失败,全部是找不到旧符号' },
|
|
83
|
+
{ sourceRef: 'run://spike-rename#diff', note: 'diff 只触及定义文件,未触及任何调用方' },
|
|
84
|
+
{ sourceRef: 'run://spike-rename#transcript', note: 'Agent 自述「已读过该文件,理解了这块代码」' },
|
|
85
|
+
];
|
|
86
|
+
const DISTILLER_CONFIDENCE = 0.76;
|
|
87
|
+
const DREAMER_BAD_DECISION = '直接重命名跨包引用的导出符号,只改了定义文件,没有枚举任何调用方。';
|
|
88
|
+
const DREAMER_BETTER_DECISION = '改动跨包引用的导出符号前,先审计文件树、grep 全部 imports、检查导出依赖图,把受影响的包列成清单,再动手改。';
|
|
89
|
+
const DREAMER_RATIONALE = '共享符号的影响面不在定义文件里而在调用方;先把影响面变成一份可核对的清单,才能判断这次改动的代价。';
|
|
90
|
+
const DREAMER_STRATEGIC_PERSPECTIVE = '最小变更面:先量出爆炸半径,再决定切口。';
|
|
91
|
+
const PHILOSOPHER_THESIS = '「理解」不是读过一个文件,而是能列出受影响的调用方清单;在跨包导出符号上,未量化的影响面等于未评估的高风险改动。';
|
|
92
|
+
const PHILOSOPHER_PRINCIPLE_TITLE = '改动跨包导出符号前先量出影响面';
|
|
93
|
+
const PHILOSOPHER_PRINCIPLE_RATIONALE = 'dreamer 给出的更优动作是审计文件树、grep 全部 imports、检查导出依赖图并列出受影响的包;其风险级别为 high,因为影响面跨包扩散。';
|
|
94
|
+
const PHILOSOPHER_PRINCIPLE_SCOPE = '修改被多个包引用的导出符号时';
|
|
95
|
+
/** 对照链:三项具体动作与 riskLevel 都在原则文本里。 */
|
|
96
|
+
const SCRIBE_TEXT_CONTROL = {
|
|
97
|
+
statement: '在修改被多个包引用的导出符号前,先审计文件树、grep 全部 imports、检查导出依赖图,把受影响的包列成清单;因为影响面跨包扩散,这类改动的风险级别为 high,清单未成形前不要动手。',
|
|
98
|
+
rationale: '影响面不在定义文件里而在调用方。三项动作(审计文件树、grep 全部 imports、检查导出依赖图)把影响面变成可核对的清单;风险级别 high 说明清单缺失时不应继续。',
|
|
99
|
+
antiPatterns: [
|
|
100
|
+
'只读定义文件就认为掌握了影响面',
|
|
101
|
+
'先改定义,再靠编译错误反查调用方',
|
|
102
|
+
'在 high 风险改动上跳过影响面清单',
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
/** 缺陷链 A:三项具体动作保留,riskLevel 维度在原则文本中完全消失。 */
|
|
106
|
+
const SCRIBE_TEXT_DEFECT_A = {
|
|
107
|
+
statement: '在修改被多个包引用的导出符号前,先审计文件树、grep 全部 imports、检查导出依赖图,把受影响的包列成清单,再动手改。',
|
|
108
|
+
rationale: '影响面不在定义文件里而在调用方。三项动作(审计文件树、grep 全部 imports、检查导出依赖图)把影响面变成可核对的清单。',
|
|
109
|
+
antiPatterns: [
|
|
110
|
+
'只读定义文件就认为掌握了影响面',
|
|
111
|
+
'先改定义,再靠编译错误反查调用方',
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
/** 缺陷链 B:riskLevel 保留,三项具体动作被模糊成「理解架构」。 */
|
|
115
|
+
const SCRIBE_TEXT_DEFECT_B = {
|
|
116
|
+
statement: '在修改被多个包引用的导出符号前,先理解架构,确认自己掌握了整体结构;因为影响面跨包扩散,这类改动的风险级别为 high,理解不到位就不要动手。',
|
|
117
|
+
rationale: '理解架构是 high 风险改动的前置条件;架构理解不到位时不应继续。',
|
|
118
|
+
antiPatterns: [
|
|
119
|
+
'在没有理解架构的情况下动手改跨包导出符号',
|
|
120
|
+
'在 high 风险改动上跳过架构理解',
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
const ARTIFICER_IMPL_CONTROL = {
|
|
124
|
+
implementationCode: [
|
|
125
|
+
'function evaluate(input) {',
|
|
126
|
+
' const touchesSharedExport = /export (const|function|class|type) /.test(input.params.newText ?? "");',
|
|
127
|
+
' const auditDone = input.params.impactedPackagesChecklist?.length > 0;',
|
|
128
|
+
' if (touchesSharedExport && !auditDone) {',
|
|
129
|
+
' return { decision: "block", reason: "high-risk shared export change without impacted-package checklist" };',
|
|
130
|
+
' }',
|
|
131
|
+
' return { decision: "allow" };',
|
|
132
|
+
'}',
|
|
133
|
+
].join('\n'),
|
|
134
|
+
implementationSummary: '跨包导出符号变更时要求先提交受影响包清单(对应三项审计动作),缺清单则按 high 风险拦截。',
|
|
135
|
+
risks: ['对单包内部私有导出可能过于严格'],
|
|
136
|
+
};
|
|
137
|
+
const ARTIFICER_IMPL_DEFECT_A = {
|
|
138
|
+
implementationCode: [
|
|
139
|
+
'function evaluate(input) {',
|
|
140
|
+
' const touchesSharedExport = /export (const|function|class|type) /.test(input.params.newText ?? "");',
|
|
141
|
+
' const auditDone = input.params.impactedPackagesChecklist?.length > 0;',
|
|
142
|
+
' if (touchesSharedExport && !auditDone) {',
|
|
143
|
+
' return { decision: "propose_correction", reason: "impacted-package checklist missing" };',
|
|
144
|
+
' }',
|
|
145
|
+
' return { decision: "allow" };',
|
|
146
|
+
'}',
|
|
147
|
+
].join('\n'),
|
|
148
|
+
implementationSummary: '跨包导出符号变更时提示补齐受影响包清单;原则文本未给出风险级别,因此未做拦截分级。',
|
|
149
|
+
risks: ['未区分风险级别,高风险改动与低风险改动同等对待'],
|
|
150
|
+
};
|
|
151
|
+
const ARTIFICER_IMPL_DEFECT_B = {
|
|
152
|
+
implementationCode: [
|
|
153
|
+
'function evaluate(input) {',
|
|
154
|
+
' const claimsArchitectureUnderstood = input.params.notes?.includes("架构") === true;',
|
|
155
|
+
' if (!claimsArchitectureUnderstood) {',
|
|
156
|
+
' return { decision: "block", reason: "architecture not understood before high-risk change" };',
|
|
157
|
+
' }',
|
|
158
|
+
' return { decision: "allow" };',
|
|
159
|
+
'}',
|
|
160
|
+
].join('\n'),
|
|
161
|
+
implementationSummary: '按原则文本要求「先理解架构」,以 notes 中是否声称理解架构作为 high 风险改动的前置判据;原则文本未给出可执行动作,因此无法校验具体审计步骤。',
|
|
162
|
+
risks: ['判据是自述而非可核验的动作,容易被空话通过'],
|
|
163
|
+
};
|
|
164
|
+
const EVALUATOR_BASELINE_CONTROL = {
|
|
165
|
+
decision: 'approved',
|
|
166
|
+
summary: '实现与原则文本一致:要求受影响包清单,缺清单按 high 风险拦截。',
|
|
167
|
+
score: 0.86,
|
|
168
|
+
strengths: ['拦截判据可核验', '与原则文本的风险分级一致'],
|
|
169
|
+
concerns: [],
|
|
170
|
+
requiredChanges: [],
|
|
171
|
+
scopeVerdict: 'precise',
|
|
172
|
+
scopeExplanation: '仅在跨包导出符号变更时触发,与原则 scope 吻合。',
|
|
173
|
+
};
|
|
174
|
+
const EVALUATOR_BASELINE_DEFECT_A = {
|
|
175
|
+
decision: 'approved',
|
|
176
|
+
summary: '实现忠实覆盖了原则文本要求的清单动作,未见与文本冲突之处。',
|
|
177
|
+
score: 0.81,
|
|
178
|
+
strengths: ['清单判据与原则文本逐条对应'],
|
|
179
|
+
concerns: [],
|
|
180
|
+
requiredChanges: [],
|
|
181
|
+
scopeVerdict: 'precise',
|
|
182
|
+
scopeExplanation: '触发条件与原则文本给出的范围一致。',
|
|
183
|
+
};
|
|
184
|
+
const EVALUATOR_BASELINE_DEFECT_B = {
|
|
185
|
+
decision: 'needs_revision',
|
|
186
|
+
summary: '实现与原则文本一致,但判据依赖自述,缺少可核验的动作。',
|
|
187
|
+
score: 0.62,
|
|
188
|
+
strengths: ['保留了 high 风险的前置门槛'],
|
|
189
|
+
concerns: ['以 notes 是否提到「架构」作为判据,容易被空话通过'],
|
|
190
|
+
requiredChanges: ['把判据换成可核验的输入字段'],
|
|
191
|
+
scopeVerdict: 'too_broad',
|
|
192
|
+
scopeExplanation: '任何变更只要 notes 提到架构即放行,范围过宽。',
|
|
193
|
+
};
|
|
194
|
+
function buildChain(spec) {
|
|
195
|
+
const { slug } = spec;
|
|
196
|
+
const rootCauseArtifactId = `pi-art-spike-${slug}-rootcause`;
|
|
197
|
+
const distillerArtifactId = `pi-art-spike-${slug}-distiller`;
|
|
198
|
+
const routerArtifactId = `pi-art-spike-${slug}-router`;
|
|
199
|
+
const dreamerArtifactId = `pi-art-spike-${slug}-dreamer`;
|
|
200
|
+
const philosopherArtifactId = `pi-art-spike-${slug}-philosopher`;
|
|
201
|
+
const scribeArtifactId = `pi-art-spike-${slug}-scribe`;
|
|
202
|
+
const artificerArtifactId = `pi-art-spike-${slug}-artificer`;
|
|
203
|
+
const evaluatorArtifactId = `pi-art-spike-${slug}-evaluator`;
|
|
204
|
+
const painTaskSuffix = `spike-${slug}`;
|
|
205
|
+
const rootCauseTaskId = `diag_rootcause-${painTaskSuffix}`;
|
|
206
|
+
const distillerTaskId = `diag_distiller-${painTaskSuffix}`;
|
|
207
|
+
const routerTaskId = `diag_router-${painTaskSuffix}`;
|
|
208
|
+
const dreamerTaskId = `dreamer-${painTaskSuffix}`;
|
|
209
|
+
const philosopherTaskId = `philosopher-${painTaskSuffix}`;
|
|
210
|
+
const scribeTaskId = `scribe-${painTaskSuffix}`;
|
|
211
|
+
const artificerTaskId = `artificer-${painTaskSuffix}`;
|
|
212
|
+
const evaluatorTaskId = `evaluator-${painTaskSuffix}`;
|
|
213
|
+
const diagnosisId = `diag-${painTaskSuffix}`;
|
|
214
|
+
const rootCauseContent = {
|
|
215
|
+
valid: true,
|
|
216
|
+
diagnosisId,
|
|
217
|
+
taskId: rootCauseTaskId,
|
|
218
|
+
summary: 'Agent 重命名跨包导出符号前没有枚举调用方,导致 4 个包编译失败。',
|
|
219
|
+
causalChain: [
|
|
220
|
+
{
|
|
221
|
+
why: 1,
|
|
222
|
+
statement: '重命名跨包导出符号后 4 个包 tsc 失败',
|
|
223
|
+
evidenceRefs: ['run://spike-rename#tsc'],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
why: 2,
|
|
227
|
+
statement: 'Agent 只改了定义文件,没有搜索调用点',
|
|
228
|
+
evidenceRefs: ['run://spike-rename#diff'],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
why: 3,
|
|
232
|
+
statement: 'Agent 把「读过这个文件」当成了「掌握了这块代码」',
|
|
233
|
+
evidenceRefs: ['run://spike-rename#transcript'],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
why: 4,
|
|
237
|
+
statement: '流程里没有任何一步要求在改共享符号前枚举依赖',
|
|
238
|
+
evidenceRefs: ['run://spike-rename#transcript'],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
why: 5,
|
|
242
|
+
statement: '缺少「改动前先量出影响面」的成文约束',
|
|
243
|
+
evidenceRefs: ['run://spike-rename#transcript'],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
rootCause: ROOT_CAUSE_TEXT,
|
|
247
|
+
rootCauseCategory: 'Assumption',
|
|
248
|
+
evidence: EVIDENCE.map((e) => ({ sourceRef: e.sourceRef, note: e.note })),
|
|
249
|
+
confidence: 0.82,
|
|
250
|
+
};
|
|
251
|
+
const distillerContent = {
|
|
252
|
+
valid: true,
|
|
253
|
+
taskId: distillerTaskId,
|
|
254
|
+
sourceRootCauseArtifactId: rootCauseArtifactId,
|
|
255
|
+
abstractedPrinciple: '改动跨包共享符号前,先把影响面变成一份可核对的清单。',
|
|
256
|
+
rationale: '根因是把「读过文件」误当成「掌握影响面」;把影响面显式列出,才能让改动代价可判断。',
|
|
257
|
+
groundedOnCorePrincipleIds: ['T-01', 'T-03'],
|
|
258
|
+
scope: 'domain',
|
|
259
|
+
confidence: DISTILLER_CONFIDENCE,
|
|
260
|
+
};
|
|
261
|
+
// diag_router 的 rootCause / evidence 来自 Stage A,confidence 来自 Stage B —
|
|
262
|
+
// 这是 diag-router-runner.postFetchTransform 强制注入的不变式,不是随手写的值。
|
|
263
|
+
const routerContent = {
|
|
264
|
+
valid: true,
|
|
265
|
+
diagnosisId,
|
|
266
|
+
summary: 'Agent 在跨包导出符号上跳过影响面枚举,需要一条成文约束。',
|
|
267
|
+
rootCause: ROOT_CAUSE_TEXT,
|
|
268
|
+
violatedPrinciples: [
|
|
269
|
+
{
|
|
270
|
+
principleId: 'T-01',
|
|
271
|
+
title: 'Survey Before Acting',
|
|
272
|
+
rationale: '改动前未梳理结构,直接动手。',
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
evidence: EVIDENCE.map((e) => ({ sourceRef: e.sourceRef, note: e.note })),
|
|
276
|
+
recommendations: [
|
|
277
|
+
{
|
|
278
|
+
kind: 'principle',
|
|
279
|
+
description: '沉淀一条「改动跨包共享符号前先量出影响面」的原则。',
|
|
280
|
+
abstractedPrinciple: '改动跨包共享符号前,先把影响面变成一份可核对的清单。',
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
confidence: DISTILLER_CONFIDENCE,
|
|
284
|
+
};
|
|
285
|
+
const dreamerContent = {
|
|
286
|
+
valid: true,
|
|
287
|
+
taskId: dreamerTaskId,
|
|
288
|
+
candidates: [
|
|
289
|
+
{
|
|
290
|
+
candidateIndex: 0,
|
|
291
|
+
badDecision: DREAMER_BAD_DECISION,
|
|
292
|
+
betterDecision: DREAMER_BETTER_DECISION,
|
|
293
|
+
rationale: DREAMER_RATIONALE,
|
|
294
|
+
confidence: 0.78,
|
|
295
|
+
riskLevel: DREAMER_RISK_LEVEL,
|
|
296
|
+
strategicPerspective: DREAMER_STRATEGIC_PERSPECTIVE,
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
// 血缘:dreamer 的 contextRefs 承载已加载前驱 artifact 的 ref
|
|
300
|
+
// (dreamer-runner.buildContext 把依赖任务的 artifact ref 推进这里)。
|
|
301
|
+
contextRefs: [routerArtifactId],
|
|
302
|
+
generatedAt: GENERATED_AT,
|
|
303
|
+
};
|
|
304
|
+
const philosopherContent = {
|
|
305
|
+
taskId: philosopherTaskId,
|
|
306
|
+
sourceDreamerArtifactId: dreamerArtifactId,
|
|
307
|
+
thesis: PHILOSOPHER_THESIS,
|
|
308
|
+
principleCandidate: {
|
|
309
|
+
title: PHILOSOPHER_PRINCIPLE_TITLE,
|
|
310
|
+
rationale: PHILOSOPHER_PRINCIPLE_RATIONALE,
|
|
311
|
+
scope: PHILOSOPHER_PRINCIPLE_SCOPE,
|
|
312
|
+
confidence: 0.75,
|
|
313
|
+
},
|
|
314
|
+
risks: ['对单包内部私有符号可能过重', '大仓 grep 成本较高'],
|
|
315
|
+
generatedAt: GENERATED_AT,
|
|
316
|
+
};
|
|
317
|
+
const scribeContent = {
|
|
318
|
+
taskId: scribeTaskId,
|
|
319
|
+
sourcePhilosopherArtifactId: philosopherArtifactId,
|
|
320
|
+
principleDraft: {
|
|
321
|
+
title: PHILOSOPHER_PRINCIPLE_TITLE,
|
|
322
|
+
statement: spec.scribeText.statement,
|
|
323
|
+
rationale: spec.scribeText.rationale,
|
|
324
|
+
applicability: ['修改被多个包引用的导出符号', '重命名或删除公共 API'],
|
|
325
|
+
antiPatterns: [...spec.scribeText.antiPatterns],
|
|
326
|
+
confidence: 0.8,
|
|
327
|
+
},
|
|
328
|
+
sourceTrace: {
|
|
329
|
+
dreamerArtifactId,
|
|
330
|
+
philosopherArtifactId,
|
|
331
|
+
},
|
|
332
|
+
risks: ['对单包内部私有符号可能过重'],
|
|
333
|
+
generatedAt: GENERATED_AT,
|
|
334
|
+
};
|
|
335
|
+
const artificerContent = {
|
|
336
|
+
taskId: artificerTaskId,
|
|
337
|
+
sourceScribeArtifactId: scribeArtifactId,
|
|
338
|
+
implementationCode: spec.artificerImpl.implementationCode,
|
|
339
|
+
goldenTraceCases: [
|
|
340
|
+
{
|
|
341
|
+
caseId: `${slug}-positive-1`,
|
|
342
|
+
kind: 'positive',
|
|
343
|
+
toolName: 'edit_file',
|
|
344
|
+
params: { path: 'packages/principles-core/src/internal-helper.ts', newText: 'const local = 1;' },
|
|
345
|
+
expectedDecision: 'allow',
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
caseId: `${slug}-negative-1`,
|
|
349
|
+
kind: 'negative',
|
|
350
|
+
toolName: 'edit_file',
|
|
351
|
+
params: { path: 'packages/principles-core/src/index.ts', newText: 'export const renamed = 1;' },
|
|
352
|
+
expectedDecision: 'block',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
affectedTools: ['edit_file'],
|
|
356
|
+
implementationSummary: spec.artificerImpl.implementationSummary,
|
|
357
|
+
risks: [...spec.artificerImpl.risks],
|
|
358
|
+
sourceTrace: {
|
|
359
|
+
scribeArtifactId,
|
|
360
|
+
philosopherArtifactId,
|
|
361
|
+
dreamerArtifactId,
|
|
362
|
+
},
|
|
363
|
+
generatedAt: GENERATED_AT,
|
|
364
|
+
};
|
|
365
|
+
const evaluatorContent = {
|
|
366
|
+
taskId: evaluatorTaskId,
|
|
367
|
+
sourceArtificerArtifactId: artificerArtifactId,
|
|
368
|
+
evaluation: {
|
|
369
|
+
decision: spec.evaluatorVerdict.decision,
|
|
370
|
+
summary: spec.evaluatorVerdict.summary,
|
|
371
|
+
score: spec.evaluatorVerdict.score,
|
|
372
|
+
strengths: [...spec.evaluatorVerdict.strengths],
|
|
373
|
+
concerns: [...spec.evaluatorVerdict.concerns],
|
|
374
|
+
requiredChanges: [...spec.evaluatorVerdict.requiredChanges],
|
|
375
|
+
},
|
|
376
|
+
sourceTrace: {
|
|
377
|
+
artificerArtifactId,
|
|
378
|
+
scribeArtifactId,
|
|
379
|
+
philosopherArtifactId,
|
|
380
|
+
dreamerArtifactId,
|
|
381
|
+
},
|
|
382
|
+
risks: [],
|
|
383
|
+
generatedAt: GENERATED_AT,
|
|
384
|
+
codeReview: {
|
|
385
|
+
intentConsistency: {
|
|
386
|
+
aligned: true,
|
|
387
|
+
explanation: '实现动作与原则文本所写的动作一致。',
|
|
388
|
+
},
|
|
389
|
+
scopePrecision: {
|
|
390
|
+
verdict: spec.evaluatorVerdict.scopeVerdict,
|
|
391
|
+
explanation: spec.evaluatorVerdict.scopeExplanation,
|
|
392
|
+
},
|
|
393
|
+
traceCoverage: {
|
|
394
|
+
sufficient: true,
|
|
395
|
+
gaps: [],
|
|
396
|
+
explanation: 'golden trace 覆盖了正例与反例各一条。',
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
chainId: spec.chainId,
|
|
402
|
+
label: spec.label,
|
|
403
|
+
expectedDefect: spec.expectedDefect,
|
|
404
|
+
diagRootCause: {
|
|
405
|
+
stage: 'diag_rootcause',
|
|
406
|
+
artifactId: rootCauseArtifactId,
|
|
407
|
+
taskId: rootCauseTaskId,
|
|
408
|
+
taskKind: 'diag_rootcause',
|
|
409
|
+
edgePredecessorArtifactId: null,
|
|
410
|
+
contentJson: rootCauseContent,
|
|
411
|
+
},
|
|
412
|
+
diagDistiller: {
|
|
413
|
+
stage: 'diag_distiller',
|
|
414
|
+
artifactId: distillerArtifactId,
|
|
415
|
+
taskId: distillerTaskId,
|
|
416
|
+
taskKind: 'diag_distiller',
|
|
417
|
+
edgePredecessorArtifactId: rootCauseArtifactId,
|
|
418
|
+
contentJson: distillerContent,
|
|
419
|
+
},
|
|
420
|
+
diagRouter: {
|
|
421
|
+
stage: 'diag_router',
|
|
422
|
+
artifactId: routerArtifactId,
|
|
423
|
+
taskId: routerTaskId,
|
|
424
|
+
taskKind: 'diag_router',
|
|
425
|
+
// 边上前驱是 distiller,不是 rootcause —— 尽管 router 同时加载了两者(F17)。
|
|
426
|
+
edgePredecessorArtifactId: distillerArtifactId,
|
|
427
|
+
contentJson: routerContent,
|
|
428
|
+
},
|
|
429
|
+
dreamer: {
|
|
430
|
+
stage: 'dreamer',
|
|
431
|
+
artifactId: dreamerArtifactId,
|
|
432
|
+
taskId: dreamerTaskId,
|
|
433
|
+
taskKind: 'dreamer',
|
|
434
|
+
edgePredecessorArtifactId: routerArtifactId,
|
|
435
|
+
contentJson: dreamerContent,
|
|
436
|
+
},
|
|
437
|
+
philosopher: {
|
|
438
|
+
stage: 'philosopher',
|
|
439
|
+
artifactId: philosopherArtifactId,
|
|
440
|
+
taskId: philosopherTaskId,
|
|
441
|
+
taskKind: 'philosopher',
|
|
442
|
+
edgePredecessorArtifactId: dreamerArtifactId,
|
|
443
|
+
contentJson: philosopherContent,
|
|
444
|
+
},
|
|
445
|
+
scribe: {
|
|
446
|
+
stage: 'scribe',
|
|
447
|
+
artifactId: scribeArtifactId,
|
|
448
|
+
taskId: scribeTaskId,
|
|
449
|
+
taskKind: 'scribe',
|
|
450
|
+
edgePredecessorArtifactId: philosopherArtifactId,
|
|
451
|
+
contentJson: scribeContent,
|
|
452
|
+
},
|
|
453
|
+
artificer: {
|
|
454
|
+
stage: 'artificer',
|
|
455
|
+
artifactId: artificerArtifactId,
|
|
456
|
+
taskId: artificerTaskId,
|
|
457
|
+
taskKind: 'artificer',
|
|
458
|
+
edgePredecessorArtifactId: scribeArtifactId,
|
|
459
|
+
contentJson: artificerContent,
|
|
460
|
+
},
|
|
461
|
+
evaluator: {
|
|
462
|
+
stage: 'evaluator',
|
|
463
|
+
artifactId: evaluatorArtifactId,
|
|
464
|
+
taskId: evaluatorTaskId,
|
|
465
|
+
taskKind: 'evaluator',
|
|
466
|
+
// 边上前驱是 artificer,不是 scribe —— 尽管 evaluator 同时加载了两者(F17)。
|
|
467
|
+
edgePredecessorArtifactId: artificerArtifactId,
|
|
468
|
+
contentJson: evaluatorContent,
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
// ── The three chains ─────────────────────────────────────────────────────────
|
|
473
|
+
/** 缺陷链 A:scribe 原则文本完全丢失 dreamer 的 `riskLevel` 维度。 */
|
|
474
|
+
export const SPIKE_CHAIN_DEFECT_A = buildChain({
|
|
475
|
+
chainId: 'defect_a_risk_level_dropped',
|
|
476
|
+
slug: 'a',
|
|
477
|
+
label: '缺陷链 A — scribe 丢失 riskLevel 维度',
|
|
478
|
+
expectedDefect: {
|
|
479
|
+
kind: 'missing_dimension',
|
|
480
|
+
segment: 'dreamer_to_scribe',
|
|
481
|
+
dimension: 'riskLevel',
|
|
482
|
+
},
|
|
483
|
+
scribeText: SCRIBE_TEXT_DEFECT_A,
|
|
484
|
+
artificerImpl: ARTIFICER_IMPL_DEFECT_A,
|
|
485
|
+
evaluatorVerdict: EVALUATOR_BASELINE_DEFECT_A,
|
|
486
|
+
});
|
|
487
|
+
/** 缺陷链 B:scribe 把 dreamer 的具体动作模糊为抽象表述。 */
|
|
488
|
+
export const SPIKE_CHAIN_DEFECT_B = buildChain({
|
|
489
|
+
chainId: 'defect_b_action_abstracted',
|
|
490
|
+
slug: 'b',
|
|
491
|
+
label: '缺陷链 B — scribe 把具体动作模糊为「理解架构」',
|
|
492
|
+
expectedDefect: {
|
|
493
|
+
kind: 'action_abstracted',
|
|
494
|
+
segment: 'dreamer_to_scribe',
|
|
495
|
+
concreteActions: CONCRETE_ACTIONS,
|
|
496
|
+
abstractedAs: ABSTRACTED_PHRASE,
|
|
497
|
+
},
|
|
498
|
+
scribeText: SCRIBE_TEXT_DEFECT_B,
|
|
499
|
+
artificerImpl: ARTIFICER_IMPL_DEFECT_B,
|
|
500
|
+
evaluatorVerdict: EVALUATOR_BASELINE_DEFECT_B,
|
|
501
|
+
});
|
|
502
|
+
/** 对照链:无缺陷。用于识别「任何输入都报缺失」的假阳性。 */
|
|
503
|
+
export const SPIKE_CHAIN_CONTROL = buildChain({
|
|
504
|
+
chainId: 'control_no_defect',
|
|
505
|
+
slug: 'control',
|
|
506
|
+
label: '对照链 — 无缺陷',
|
|
507
|
+
expectedDefect: { kind: 'none' },
|
|
508
|
+
scribeText: SCRIBE_TEXT_CONTROL,
|
|
509
|
+
artificerImpl: ARTIFICER_IMPL_CONTROL,
|
|
510
|
+
evaluatorVerdict: EVALUATOR_BASELINE_CONTROL,
|
|
511
|
+
});
|
|
512
|
+
export const SPIKE_CHAINS = [
|
|
513
|
+
SPIKE_CHAIN_DEFECT_A,
|
|
514
|
+
SPIKE_CHAIN_DEFECT_B,
|
|
515
|
+
SPIKE_CHAIN_CONTROL,
|
|
516
|
+
];
|
|
517
|
+
function pushIfMismatch(out, chainId, stage, field, expected, actual) {
|
|
518
|
+
if (actual !== expected) {
|
|
519
|
+
out.push({ chainId, stage, field, expected, actual: actual ?? '<absent>' });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Collect every lineage inconsistency in a chain.
|
|
524
|
+
*
|
|
525
|
+
* Only fields that actually exist on the real output types are checked, and each
|
|
526
|
+
* check names the concrete field and the hop it must point at — so a failure
|
|
527
|
+
* reports *which* field of *which* stage drifted, not merely "chain invalid"
|
|
528
|
+
* (ERR-088: the assertion signal must identify the intended path uniquely).
|
|
529
|
+
*
|
|
530
|
+
* Checked invariants:
|
|
531
|
+
* - `taskKind` equals `stage` (F1: stage identity comes from taskKind, never artifactKind)
|
|
532
|
+
* - each hop's `edgePredecessorArtifactId` is the previous hop on the task graph edge
|
|
533
|
+
* - `contentJson.taskId` equals the hop's own `taskId` (every stage except
|
|
534
|
+
* `diag_router`, whose output carries no `taskId` — PRI-272 removed it)
|
|
535
|
+
* - `diag_distiller.sourceRootCauseArtifactId` → rootcause artifact
|
|
536
|
+
* - `diag_router` carries Stage A's `rootCause`/`evidence` and Stage B's
|
|
537
|
+
* `confidence` (the invariants `diag-router-runner.postFetchTransform` injects)
|
|
538
|
+
* - `dreamer.contextRefs` contains the router artifact
|
|
539
|
+
* - `philosopher.sourceDreamerArtifactId` → dreamer artifact
|
|
540
|
+
* - scribe / artificer / evaluator top-level source ids and every
|
|
541
|
+
* `sourceTrace.*` id point at the actual upstream artifacts
|
|
542
|
+
*/
|
|
543
|
+
export function findSpikeLineageViolations(chain) {
|
|
544
|
+
const violations = [];
|
|
545
|
+
const { chainId } = chain;
|
|
546
|
+
const hops = spikeChainHops(chain);
|
|
547
|
+
// taskKind ⟺ stage, and edge predecessor chaining.
|
|
548
|
+
for (let i = 0; i < hops.length; i++) {
|
|
549
|
+
const hop = hops[i];
|
|
550
|
+
if (!hop)
|
|
551
|
+
continue;
|
|
552
|
+
if (hop.taskKind !== hop.stage) {
|
|
553
|
+
violations.push({
|
|
554
|
+
chainId,
|
|
555
|
+
stage: hop.stage,
|
|
556
|
+
field: 'taskKind',
|
|
557
|
+
expected: hop.stage,
|
|
558
|
+
actual: hop.taskKind,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
const previous = i === 0 ? undefined : hops[i - 1];
|
|
562
|
+
const expectedPredecessor = previous ? previous.artifactId : null;
|
|
563
|
+
if (hop.edgePredecessorArtifactId !== expectedPredecessor) {
|
|
564
|
+
violations.push({
|
|
565
|
+
chainId,
|
|
566
|
+
stage: hop.stage,
|
|
567
|
+
field: 'edgePredecessorArtifactId',
|
|
568
|
+
expected: expectedPredecessor ?? '<null>',
|
|
569
|
+
actual: hop.edgePredecessorArtifactId ?? '<null>',
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// contentJson.taskId ⟺ hop.taskId. diag_router excluded: DiagnosticianOutputV1
|
|
574
|
+
// has no taskId field (removed in PRI-272 so the LLM cannot fabricate lineage).
|
|
575
|
+
const taskIdBearingHops = [
|
|
576
|
+
{ hop: chain.diagRootCause },
|
|
577
|
+
{ hop: chain.diagDistiller },
|
|
578
|
+
{ hop: chain.dreamer },
|
|
579
|
+
{ hop: chain.philosopher },
|
|
580
|
+
{ hop: chain.scribe },
|
|
581
|
+
{ hop: chain.artificer },
|
|
582
|
+
{ hop: chain.evaluator },
|
|
583
|
+
];
|
|
584
|
+
for (const { hop } of taskIdBearingHops) {
|
|
585
|
+
pushIfMismatch(violations, chainId, hop.stage, 'taskId', hop.taskId, hop.contentJson.taskId);
|
|
586
|
+
}
|
|
587
|
+
// diag_distiller → diag_rootcause
|
|
588
|
+
pushIfMismatch(violations, chainId, 'diag_distiller', 'sourceRootCauseArtifactId', chain.diagRootCause.artifactId, chain.diagDistiller.contentJson.sourceRootCauseArtifactId);
|
|
589
|
+
// diag_router invariants injected from Stage A / Stage B.
|
|
590
|
+
pushIfMismatch(violations, chainId, 'diag_router', 'rootCause', chain.diagRootCause.contentJson.rootCause, chain.diagRouter.contentJson.rootCause);
|
|
591
|
+
pushIfMismatch(violations, chainId, 'diag_router', 'confidence', String(chain.diagDistiller.contentJson.confidence), String(chain.diagRouter.contentJson.confidence));
|
|
592
|
+
const stageAEvidence = chain.diagRootCause.contentJson.evidence;
|
|
593
|
+
const routerEvidence = chain.diagRouter.contentJson.evidence;
|
|
594
|
+
if (routerEvidence.length !== stageAEvidence.length) {
|
|
595
|
+
violations.push({
|
|
596
|
+
chainId,
|
|
597
|
+
stage: 'diag_router',
|
|
598
|
+
field: 'evidence.length',
|
|
599
|
+
expected: String(stageAEvidence.length),
|
|
600
|
+
actual: String(routerEvidence.length),
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
for (let i = 0; i < stageAEvidence.length; i++) {
|
|
605
|
+
const expected = stageAEvidence[i];
|
|
606
|
+
const actual = routerEvidence[i];
|
|
607
|
+
if (!expected || !actual)
|
|
608
|
+
continue;
|
|
609
|
+
pushIfMismatch(violations, chainId, 'diag_router', `evidence[${i}].sourceRef`, expected.sourceRef, actual.sourceRef);
|
|
610
|
+
pushIfMismatch(violations, chainId, 'diag_router', `evidence[${i}].note`, expected.note, actual.note);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// dreamer ← diag_router (contextRefs carries the loaded predecessor artifact ref)
|
|
614
|
+
if (!chain.dreamer.contentJson.contextRefs.includes(chain.diagRouter.artifactId)) {
|
|
615
|
+
violations.push({
|
|
616
|
+
chainId,
|
|
617
|
+
stage: 'dreamer',
|
|
618
|
+
field: 'contextRefs',
|
|
619
|
+
expected: `contains ${chain.diagRouter.artifactId}`,
|
|
620
|
+
actual: chain.dreamer.contentJson.contextRefs.join(',') || '<empty>',
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
// philosopher → dreamer
|
|
624
|
+
pushIfMismatch(violations, chainId, 'philosopher', 'sourceDreamerArtifactId', chain.dreamer.artifactId, chain.philosopher.contentJson.sourceDreamerArtifactId);
|
|
625
|
+
// scribe → philosopher (+ dreamer via sourceTrace)
|
|
626
|
+
pushIfMismatch(violations, chainId, 'scribe', 'sourcePhilosopherArtifactId', chain.philosopher.artifactId, chain.scribe.contentJson.sourcePhilosopherArtifactId);
|
|
627
|
+
pushIfMismatch(violations, chainId, 'scribe', 'sourceTrace.philosopherArtifactId', chain.philosopher.artifactId, chain.scribe.contentJson.sourceTrace.philosopherArtifactId);
|
|
628
|
+
pushIfMismatch(violations, chainId, 'scribe', 'sourceTrace.dreamerArtifactId', chain.dreamer.artifactId, chain.scribe.contentJson.sourceTrace.dreamerArtifactId);
|
|
629
|
+
// artificer → scribe (+ philosopher / dreamer via sourceTrace)
|
|
630
|
+
pushIfMismatch(violations, chainId, 'artificer', 'sourceScribeArtifactId', chain.scribe.artifactId, chain.artificer.contentJson.sourceScribeArtifactId);
|
|
631
|
+
pushIfMismatch(violations, chainId, 'artificer', 'sourceTrace.scribeArtifactId', chain.scribe.artifactId, chain.artificer.contentJson.sourceTrace.scribeArtifactId);
|
|
632
|
+
pushIfMismatch(violations, chainId, 'artificer', 'sourceTrace.philosopherArtifactId', chain.philosopher.artifactId, chain.artificer.contentJson.sourceTrace.philosopherArtifactId);
|
|
633
|
+
pushIfMismatch(violations, chainId, 'artificer', 'sourceTrace.dreamerArtifactId', chain.dreamer.artifactId, chain.artificer.contentJson.sourceTrace.dreamerArtifactId);
|
|
634
|
+
// evaluator → artificer (+ scribe / philosopher / dreamer via sourceTrace)
|
|
635
|
+
pushIfMismatch(violations, chainId, 'evaluator', 'sourceArtificerArtifactId', chain.artificer.artifactId, chain.evaluator.contentJson.sourceArtificerArtifactId);
|
|
636
|
+
pushIfMismatch(violations, chainId, 'evaluator', 'sourceTrace.artificerArtifactId', chain.artificer.artifactId, chain.evaluator.contentJson.sourceTrace.artificerArtifactId);
|
|
637
|
+
pushIfMismatch(violations, chainId, 'evaluator', 'sourceTrace.scribeArtifactId', chain.scribe.artifactId, chain.evaluator.contentJson.sourceTrace.scribeArtifactId);
|
|
638
|
+
pushIfMismatch(violations, chainId, 'evaluator', 'sourceTrace.philosopherArtifactId', chain.philosopher.artifactId, chain.evaluator.contentJson.sourceTrace.philosopherArtifactId);
|
|
639
|
+
pushIfMismatch(violations, chainId, 'evaluator', 'sourceTrace.dreamerArtifactId', chain.dreamer.artifactId, chain.evaluator.contentJson.sourceTrace.dreamerArtifactId);
|
|
640
|
+
return violations;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Throw with a per-field report when a chain's lineage is inconsistent.
|
|
644
|
+
*
|
|
645
|
+
* Used by the fixture self-check test and available to the Spike driver so a
|
|
646
|
+
* corrupted fixture fails loud instead of silently weakening the Spike (rc-9).
|
|
647
|
+
*/
|
|
648
|
+
export function assertSpikeChainLineageConsistent(chain) {
|
|
649
|
+
const violations = findSpikeLineageViolations(chain);
|
|
650
|
+
if (violations.length === 0)
|
|
651
|
+
return;
|
|
652
|
+
const detail = violations
|
|
653
|
+
.map((v) => `${v.stage}.${v.field}: expected ${v.expected}, got ${v.actual}`)
|
|
654
|
+
.join('\n ');
|
|
655
|
+
throw new Error(`Spike chain "${chain.chainId}" lineage is inconsistent (${violations.length} violation(s)):\n ${detail}`);
|
|
656
|
+
}
|
|
657
|
+
//# sourceMappingURL=progressive-disclosure-spike-fixtures.js.map
|