@sigloch/contracts 6.3.0 → 10.0.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/se/ao-rules.d.ts +12 -12
- package/dist/se/ao-rules.js +88 -225
- package/dist/se/conformance-rules.d.ts +28 -0
- package/dist/se/conformance-rules.js +40 -17
- package/dist/se/cr-quality-rules.js +75 -58
- package/dist/se/evaluate-all.d.ts +4 -2
- package/dist/se/evaluate-all.js +40 -24
- package/dist/se/fchain-quality-rules.d.ts +0 -1
- package/dist/se/fchain-quality-rules.js +0 -44
- package/dist/se/flat-graph.d.ts +9 -1
- package/dist/se/flat-graph.js +15 -6
- package/dist/se/format-e-parser.d.ts +14 -2
- package/dist/se/format-e-parser.js +33 -9
- package/dist/se/grammar-snapshot.d.ts +10 -7
- package/dist/se/grammar-snapshot.js +172 -27
- package/dist/se/index.d.ts +4 -3
- package/dist/se/index.js +4 -3
- package/dist/se/meta-model.d.ts +99 -7
- package/dist/se/meta-model.js +155 -14
- package/dist/se/metric-rules.d.ts +20 -1
- package/dist/se/metric-rules.js +97 -58
- package/dist/se/module-crossings.d.ts +102 -0
- package/dist/se/module-crossings.js +196 -0
- package/dist/se/near-duplicate-rules.d.ts +16 -24
- package/dist/se/near-duplicate-rules.js +21 -92
- package/dist/se/ontology.d.ts +32 -47
- package/dist/se/ontology.js +56 -13
- package/dist/se/policy.d.ts +6 -0
- package/dist/se/policy.js +53 -2
- package/dist/se/quality-rules.d.ts +18 -0
- package/dist/se/quality-rules.js +68 -12
- package/dist/se/readiness.d.ts +7 -5
- package/dist/se/readiness.js +40 -22
- package/dist/se/rules.d.ts +6 -7
- package/dist/se/rules.js +358 -128
- package/dist/se/schema-quality-rules.d.ts +0 -1
- package/dist/se/schema-quality-rules.js +7 -28
- package/dist/se/similarity.d.ts +61 -0
- package/dist/se/similarity.js +116 -0
- package/dist/se/uc-quality-rules.js +31 -12
- package/package.json +3 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { indexOf } from './graph-index.js';
|
|
2
|
+
const CACHE = new WeakMap();
|
|
3
|
+
function build(graph) {
|
|
4
|
+
const idx = indexOf(graph);
|
|
5
|
+
// FUNC → MOD (`FUNC -allocate-> MOD` ist `0..1`, also hoechstens ein Modul je FUNC).
|
|
6
|
+
const modOfFunc = new Map();
|
|
7
|
+
for (const mod of idx.elementsOfType('MOD')) {
|
|
8
|
+
for (const t of idx.in(mod.id, 'allocate')) {
|
|
9
|
+
if (idx.typeOf(t.source) === 'FUNC')
|
|
10
|
+
modOfFunc.set(t.source, mod.id);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// CR-SM-282: die Besitzkette eines MODs — es selbst plus seine `compose`-Vorfahren.
|
|
14
|
+
// `byModule` braucht sie, `pairs` nicht (s. Kopf).
|
|
15
|
+
const parentMod = new Map();
|
|
16
|
+
for (const mod of idx.elementsOfType('MOD')) {
|
|
17
|
+
for (const t of idx.in(mod.id, 'compose')) {
|
|
18
|
+
if (idx.typeOf(t.source) === 'MOD')
|
|
19
|
+
parentMod.set(mod.id, t.source);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const chainOf = (modId) => {
|
|
23
|
+
const out = [];
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
let cur = modId;
|
|
26
|
+
while (cur !== undefined && !seen.has(cur)) {
|
|
27
|
+
seen.add(cur);
|
|
28
|
+
out.push(cur);
|
|
29
|
+
cur = parentMod.get(cur);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
};
|
|
33
|
+
const ownersOfFunc = new Map();
|
|
34
|
+
for (const [func, mod] of modOfFunc)
|
|
35
|
+
ownersOfFunc.set(func, chainOf(mod));
|
|
36
|
+
// CR-SM-283: dieselbe Konstruktion auf dem FUNC-compose-Baum. `funcChainOf(f)` ist f selbst
|
|
37
|
+
// plus seine Vorfahren; nur ZERLEGTE FUNCs sind Whiteboxes und kommen als Kandidat in Frage.
|
|
38
|
+
const parentFunc = new Map();
|
|
39
|
+
const decomposed = new Set();
|
|
40
|
+
for (const t of graph.traces) {
|
|
41
|
+
if (t.type !== 'compose')
|
|
42
|
+
continue;
|
|
43
|
+
if (idx.typeOf(t.source) !== 'FUNC' || idx.typeOf(t.target) !== 'FUNC')
|
|
44
|
+
continue;
|
|
45
|
+
parentFunc.set(t.target, t.source);
|
|
46
|
+
decomposed.add(t.source);
|
|
47
|
+
}
|
|
48
|
+
const funcChain = new Map();
|
|
49
|
+
const funcChainOf = (funcId) => {
|
|
50
|
+
const cached = funcChain.get(funcId);
|
|
51
|
+
if (cached)
|
|
52
|
+
return cached;
|
|
53
|
+
const out = [];
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
let cur = funcId;
|
|
56
|
+
while (cur !== undefined && !seen.has(cur)) {
|
|
57
|
+
seen.add(cur);
|
|
58
|
+
out.push(cur);
|
|
59
|
+
cur = parentFunc.get(cur);
|
|
60
|
+
}
|
|
61
|
+
funcChain.set(funcId, out);
|
|
62
|
+
return out;
|
|
63
|
+
};
|
|
64
|
+
const pairs = new Map();
|
|
65
|
+
const byModule = new Map();
|
|
66
|
+
const byFunc = new Map();
|
|
67
|
+
const afferentContracts = new Map();
|
|
68
|
+
const efferentContracts = new Map();
|
|
69
|
+
const addInto = (m, key, contracts) => {
|
|
70
|
+
let set = m.get(key);
|
|
71
|
+
if (!set) {
|
|
72
|
+
set = new Set();
|
|
73
|
+
m.set(key, set);
|
|
74
|
+
}
|
|
75
|
+
for (const c of contracts)
|
|
76
|
+
set.add(c);
|
|
77
|
+
};
|
|
78
|
+
const addTo = (mod, contracts) => addInto(byModule, mod, contracts);
|
|
79
|
+
for (const flow of idx.elementsOfType('FLOW')) {
|
|
80
|
+
const producerMods = new Set(idx.in(flow.id, 'io')
|
|
81
|
+
.filter(t => idx.typeOf(t.source) === 'FUNC')
|
|
82
|
+
.map(t => modOfFunc.get(t.source))
|
|
83
|
+
.filter((m) => !!m));
|
|
84
|
+
const consumerMods = new Set(idx.out(flow.id, 'io')
|
|
85
|
+
.filter(t => idx.typeOf(t.target) === 'FUNC')
|
|
86
|
+
.map(t => modOfFunc.get(t.target))
|
|
87
|
+
.filter((m) => !!m));
|
|
88
|
+
const schemas = idx.out(flow.id, 'relation')
|
|
89
|
+
.filter(t => idx.typeOf(t.target) === 'SCHEMA')
|
|
90
|
+
.map(t => t.target);
|
|
91
|
+
const contracts = schemas.length > 0 ? schemas : [`UNBOUND:${flow.id}`];
|
|
92
|
+
// CR-SM-283: derselbe Test auf dem compose-Baum. `allEndpoints` statt `endpoints` —
|
|
93
|
+
// die FUNC-Whitebox kennt keine Modul-Zugehoerigkeit, eine unallozierte FUNC liegt
|
|
94
|
+
// trotzdem drinnen oder draussen.
|
|
95
|
+
const allEndpoints = [
|
|
96
|
+
...idx.in(flow.id, 'io').filter(t => idx.typeOf(t.source) === 'FUNC').map(t => t.source),
|
|
97
|
+
...idx.out(flow.id, 'io').filter(t => idx.typeOf(t.target) === 'FUNC').map(t => t.target),
|
|
98
|
+
];
|
|
99
|
+
const funcCandidates = new Set(allEndpoints.flatMap(f => funcChainOf(f)).filter(f => decomposed.has(f)));
|
|
100
|
+
for (const w of funcCandidates) {
|
|
101
|
+
const inside = allEndpoints.some(f => funcChainOf(f).includes(w));
|
|
102
|
+
const outside = allEndpoints.some(f => !funcChainOf(f).includes(w));
|
|
103
|
+
if (inside && outside) {
|
|
104
|
+
let set = byFunc.get(w);
|
|
105
|
+
if (!set) {
|
|
106
|
+
set = new Set();
|
|
107
|
+
byFunc.set(w, set);
|
|
108
|
+
}
|
|
109
|
+
for (const c of contracts)
|
|
110
|
+
set.add(c);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Der MOD-Teil ab hier: ohne Zuordnung auf BEIDEN Seiten gibt es keine Modulgrenze.
|
|
114
|
+
// Der FUNC-Teil oben haengt bewusst NICHT daran — eine Whitebox ist eine Whitebox,
|
|
115
|
+
// auch wenn ihre Blaetter noch keinem Modul zugewiesen sind (R-22 meldet das separat).
|
|
116
|
+
if (producerMods.size === 0 || consumerMods.size === 0)
|
|
117
|
+
continue;
|
|
118
|
+
for (const a of producerMods) {
|
|
119
|
+
for (const b of consumerMods) {
|
|
120
|
+
if (a === b)
|
|
121
|
+
continue;
|
|
122
|
+
const [modA, modB] = a < b ? [a, b] : [b, a];
|
|
123
|
+
const key = `${modA}::${modB}`;
|
|
124
|
+
let entry = pairs.get(key);
|
|
125
|
+
if (!entry) {
|
|
126
|
+
entry = { modA, modB, contracts: new Set() };
|
|
127
|
+
pairs.set(key, entry);
|
|
128
|
+
}
|
|
129
|
+
for (const c of contracts)
|
|
130
|
+
entry.contracts.add(c);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// byModule MIT Rollup (CR-SM-282): ein Vertrag quert den Rand von `m`, wenn ein Endpunkt
|
|
134
|
+
// in `m`s Besitzkette liegt und einer nicht. Ohne Eltern-MODs faellt das exakt auf die
|
|
135
|
+
// frueheren `addTo(a)/addTo(b)` zurueck — das ist die Regressions-Invariante.
|
|
136
|
+
const endpoints = [
|
|
137
|
+
...idx.in(flow.id, 'io').filter(t => idx.typeOf(t.source) === 'FUNC').map(t => t.source),
|
|
138
|
+
...idx.out(flow.id, 'io').filter(t => idx.typeOf(t.target) === 'FUNC').map(t => t.target),
|
|
139
|
+
].filter(f => ownersOfFunc.has(f));
|
|
140
|
+
const candidates = new Set(endpoints.flatMap(f => ownersOfFunc.get(f)));
|
|
141
|
+
for (const m of candidates) {
|
|
142
|
+
const inside = endpoints.some(f => ownersOfFunc.get(f).includes(m));
|
|
143
|
+
const outside = endpoints.some(f => !ownersOfFunc.get(f).includes(m));
|
|
144
|
+
if (inside && outside)
|
|
145
|
+
addTo(m, contracts);
|
|
146
|
+
}
|
|
147
|
+
// CR-SM-293: dieselben Endpunkte, nach Seite getrennt. Ein Modul, das auf BEIDEN
|
|
148
|
+
// Seiten steht (es produziert und konsumiert diesen Vertrag im eigenen Teilbaum),
|
|
149
|
+
// faellt aus beiden Mengen — der Vertrag quert seinen Rand nicht.
|
|
150
|
+
const producerChain = new Set(idx.in(flow.id, 'io')
|
|
151
|
+
.filter(t => idx.typeOf(t.source) === 'FUNC' && ownersOfFunc.has(t.source))
|
|
152
|
+
.flatMap(t => ownersOfFunc.get(t.source)));
|
|
153
|
+
const consumerChain = new Set(idx.out(flow.id, 'io')
|
|
154
|
+
.filter(t => idx.typeOf(t.target) === 'FUNC' && ownersOfFunc.has(t.target))
|
|
155
|
+
.flatMap(t => ownersOfFunc.get(t.target)));
|
|
156
|
+
for (const m of producerChain)
|
|
157
|
+
if (!consumerChain.has(m))
|
|
158
|
+
addInto(afferentContracts, m, contracts);
|
|
159
|
+
for (const m of consumerChain)
|
|
160
|
+
if (!producerChain.has(m))
|
|
161
|
+
addInto(efferentContracts, m, contracts);
|
|
162
|
+
}
|
|
163
|
+
const funcsByModule = new Map();
|
|
164
|
+
for (const [func, owners] of ownersOfFunc) {
|
|
165
|
+
for (const m of owners) {
|
|
166
|
+
let set = funcsByModule.get(m);
|
|
167
|
+
if (!set) {
|
|
168
|
+
set = new Set();
|
|
169
|
+
funcsByModule.set(m, set);
|
|
170
|
+
}
|
|
171
|
+
set.add(func);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { pairs, byModule, byFunc, funcsByModule, afferentContracts, efferentContracts };
|
|
175
|
+
}
|
|
176
|
+
/** Die querenden Vertraege dieses Graphen — einmal je Graph-Objekt berechnet. */
|
|
177
|
+
export function moduleCrossings(graph) {
|
|
178
|
+
let result = CACHE.get(graph);
|
|
179
|
+
if (result === undefined) {
|
|
180
|
+
result = build(graph);
|
|
181
|
+
CACHE.set(graph, result);
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
/** Zahl der verschiedenen Vertraege, die den Rand dieses Moduls queren. */
|
|
186
|
+
export function crossingContractCount(graph, modId) {
|
|
187
|
+
return moduleCrossings(graph).byModule.get(modId)?.size ?? 0;
|
|
188
|
+
}
|
|
189
|
+
/** Zahl der verschiedenen Vertraege, die den Rand dieser FUNC-Whitebox queren — CR-SM-283. */
|
|
190
|
+
export function whiteboxContractCount(graph, funcId) {
|
|
191
|
+
return moduleCrossings(graph).byFunc.get(funcId)?.size ?? 0;
|
|
192
|
+
}
|
|
193
|
+
/** Die FUNCs im Teilbaum dieses Moduls (direkt alloziert + die seiner Sub-MODs) — CR-SM-282. */
|
|
194
|
+
export function subtreeFuncs(graph, modId) {
|
|
195
|
+
return moduleCrossings(graph).funcsByModule.get(modId) ?? new Set();
|
|
196
|
+
}
|
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CR-121 P2: Near-Duplicate Detection for FUNC and SCHEMA elements.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* CR-SM-286: **die Aehnlichkeit wird HIER gerechnet, nicht injiziert.**
|
|
5
|
+
*
|
|
6
|
+
* Bis hierher trugen beide Regeln `if (!_ndXXMatrix) return [];` und warteten auf
|
|
7
|
+
* `setND01SimilarityMatrix()` / `setND02SimilarityMatrix()`. Die Formel stand als Prosa in
|
|
8
|
+
* diesem Kopf, die Implementierung in `graphcode/src/kernel/measure/nd-similarity.ts`. Folgen,
|
|
9
|
+
* alle gemessen: bei severity `error` ununterscheidbares Fail-open (moneyflow trug 16
|
|
10
|
+
* ND-01-Befunde, die ausserhalb graphcodes niemand sah), prozessweiter Modulzustand (Graph A
|
|
11
|
+
* urteilte ueber Graph B, wogegen graphcode eine `finally`-Klammer brauchte), und AO-D01 —
|
|
12
|
+
* eine GATE-Regel — schwaechte sich stillschweigend ab ("no matrix → assume pass").
|
|
13
|
+
*
|
|
14
|
+
* Jetzt: `funcSimilarity(graph)` / `schemaSimilarity(graph)` aus `similarity.ts`, je Graph
|
|
15
|
+
* gecacht. Kein Setter, kein Modulzustand, kein Fail-open. Formeln und Schwelle 0,85 sind
|
|
16
|
+
* zeichengleich uebernommen — dasselbe Urteil an der richtigen Stelle.
|
|
4
17
|
*/
|
|
5
18
|
import type { OntologyGraph } from './ontology.js';
|
|
6
19
|
import type { RuleViolation } from './rules.js';
|
|
7
|
-
/**
|
|
8
|
-
* Inject pre-computed similarity matrix for FUNC elements.
|
|
9
|
-
* Similarity = 0.35 × descr_jaccard + 0.25 × verb_match + 0.25 × io_topology + 0.15 × req_overlap
|
|
10
|
-
*/
|
|
11
|
-
export declare function setND01SimilarityMatrix(data: {
|
|
12
|
-
funcIds: string[];
|
|
13
|
-
matrix: number[][];
|
|
14
|
-
} | null): void;
|
|
15
|
-
/** ND-01: FUNC pairs with similarity >= 0.85 → error. */
|
|
20
|
+
/** ND-01: FUNC pairs with similarity >= 0.85 -> error. */
|
|
16
21
|
export declare function nd01FuncNearDuplicate(graph: OntologyGraph): RuleViolation[];
|
|
17
|
-
/**
|
|
18
|
-
* Inject pre-computed similarity matrix for SCHEMA elements.
|
|
19
|
-
* Similarity = 0.50 × field_jaccard + 0.30 × descr_jaccard + 0.20 × usage_overlap
|
|
20
|
-
*/
|
|
21
|
-
export declare function setND02SimilarityMatrix(data: {
|
|
22
|
-
schemaIds: string[];
|
|
23
|
-
matrix: number[][];
|
|
24
|
-
} | null): void;
|
|
25
|
-
/** Read-only access to current ND-02 matrix (used by AO-D01 for schema overlap). */
|
|
26
|
-
export declare function getND02SimilarityMatrix(): {
|
|
27
|
-
schemaIds: string[];
|
|
28
|
-
matrix: number[][];
|
|
29
|
-
} | null;
|
|
30
|
-
/** ND-02: SCHEMA pairs with similarity >= 0.85 → error. */
|
|
22
|
+
/** ND-02: SCHEMA pairs with similarity >= 0.85 -> error. */
|
|
31
23
|
export declare function nd02SchemaNearDuplicate(graph: OntologyGraph): RuleViolation[];
|
|
32
24
|
export declare const ND_RULES: readonly [{
|
|
33
25
|
readonly id: "ND-01";
|
|
@@ -1,101 +1,30 @@
|
|
|
1
|
+
import { funcSimilarity, schemaSimilarity, pairsAbove } from './similarity.js';
|
|
1
2
|
const ND_SIMILARITY_THRESHOLD = 0.85;
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
/** Der gemeinsame Rumpf beider Regeln — eine Schleife, zwei Meldungstexte. */
|
|
4
|
+
function nearDuplicates(graph, sim, ruleId, what, fixHint) {
|
|
5
|
+
const byId = new Map(graph.elements.map(e => [e.id, e]));
|
|
6
|
+
return pairsAbove(sim, ND_SIMILARITY_THRESHOLD).map(({ a, b, similarity }) => {
|
|
7
|
+
const elA = byId.get(a);
|
|
8
|
+
const elB = byId.get(b);
|
|
9
|
+
const pct = Math.round(similarity * 100);
|
|
10
|
+
return {
|
|
11
|
+
rule_id: ruleId,
|
|
12
|
+
severity: 'error',
|
|
13
|
+
element_id: b,
|
|
14
|
+
message: `${b}${elB?.name ? ' (' + elB.name + ')' : ''} is ${pct}% similar to ${a}${elA?.name ? ' (' + elA.name + ')' : ''} — potential duplicate ${what}`,
|
|
15
|
+
fix_hint: fixHint,
|
|
16
|
+
context: { element_type: elB?.type, element_name: elB?.name },
|
|
17
|
+
};
|
|
18
|
+
});
|
|
12
19
|
}
|
|
13
|
-
/** ND-01: FUNC pairs with similarity >= 0.85
|
|
20
|
+
/** ND-01: FUNC pairs with similarity >= 0.85 -> error. */
|
|
14
21
|
export function nd01FuncNearDuplicate(graph) {
|
|
15
|
-
|
|
16
|
-
return [];
|
|
17
|
-
const { funcIds, matrix } = _nd01Matrix;
|
|
18
|
-
const violations = [];
|
|
19
|
-
const seen = new Set();
|
|
20
|
-
for (let i = 0; i < funcIds.length; i++) {
|
|
21
|
-
for (let j = i + 1; j < funcIds.length; j++) {
|
|
22
|
-
const sim = matrix[i][j];
|
|
23
|
-
if (sim < ND_SIMILARITY_THRESHOLD)
|
|
24
|
-
continue;
|
|
25
|
-
const pairKey = `${funcIds[i]}:${funcIds[j]}`;
|
|
26
|
-
if (seen.has(pairKey))
|
|
27
|
-
continue;
|
|
28
|
-
seen.add(pairKey);
|
|
29
|
-
const pct = Math.round(sim * 100);
|
|
30
|
-
const elI = graph.elements.find(e => e.id === funcIds[i]);
|
|
31
|
-
const elJ = graph.elements.find(e => e.id === funcIds[j]);
|
|
32
|
-
violations.push({
|
|
33
|
-
rule_id: 'ND-01',
|
|
34
|
-
severity: 'error',
|
|
35
|
-
element_id: funcIds[j],
|
|
36
|
-
message: `${funcIds[j]}${elJ?.name ? ' (' + elJ.name + ')' : ''} is ${pct}% similar to ${funcIds[i]}${elI?.name ? ' (' + elI.name + ')' : ''} — potential duplicate function`,
|
|
37
|
-
fix_hint: 'Merge into single FUNC or differentiate responsibilities',
|
|
38
|
-
context: {
|
|
39
|
-
element_type: elJ?.type,
|
|
40
|
-
element_name: elJ?.name,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return violations;
|
|
22
|
+
return nearDuplicates(graph, funcSimilarity(graph), 'ND-01', 'function', 'Merge into single FUNC or differentiate responsibilities');
|
|
46
23
|
}
|
|
47
|
-
|
|
48
|
-
// ND-02: SCHEMA near-duplicates
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
let _nd02Matrix = null;
|
|
51
|
-
/**
|
|
52
|
-
* Inject pre-computed similarity matrix for SCHEMA elements.
|
|
53
|
-
* Similarity = 0.50 × field_jaccard + 0.30 × descr_jaccard + 0.20 × usage_overlap
|
|
54
|
-
*/
|
|
55
|
-
export function setND02SimilarityMatrix(data) {
|
|
56
|
-
_nd02Matrix = data;
|
|
57
|
-
}
|
|
58
|
-
/** Read-only access to current ND-02 matrix (used by AO-D01 for schema overlap). */
|
|
59
|
-
export function getND02SimilarityMatrix() {
|
|
60
|
-
return _nd02Matrix;
|
|
61
|
-
}
|
|
62
|
-
/** ND-02: SCHEMA pairs with similarity >= 0.85 → error. */
|
|
24
|
+
/** ND-02: SCHEMA pairs with similarity >= 0.85 -> error. */
|
|
63
25
|
export function nd02SchemaNearDuplicate(graph) {
|
|
64
|
-
|
|
65
|
-
return [];
|
|
66
|
-
const { schemaIds, matrix } = _nd02Matrix;
|
|
67
|
-
const violations = [];
|
|
68
|
-
const seen = new Set();
|
|
69
|
-
for (let i = 0; i < schemaIds.length; i++) {
|
|
70
|
-
for (let j = i + 1; j < schemaIds.length; j++) {
|
|
71
|
-
const sim = matrix[i][j];
|
|
72
|
-
if (sim < ND_SIMILARITY_THRESHOLD)
|
|
73
|
-
continue;
|
|
74
|
-
const pairKey = `${schemaIds[i]}:${schemaIds[j]}`;
|
|
75
|
-
if (seen.has(pairKey))
|
|
76
|
-
continue;
|
|
77
|
-
seen.add(pairKey);
|
|
78
|
-
const pct = Math.round(sim * 100);
|
|
79
|
-
const elI = graph.elements.find(e => e.id === schemaIds[i]);
|
|
80
|
-
const elJ = graph.elements.find(e => e.id === schemaIds[j]);
|
|
81
|
-
violations.push({
|
|
82
|
-
rule_id: 'ND-02',
|
|
83
|
-
severity: 'error',
|
|
84
|
-
element_id: schemaIds[j],
|
|
85
|
-
message: `${schemaIds[j]}${elJ?.name ? ' (' + elJ.name + ')' : ''} is ${pct}% similar to ${schemaIds[i]}${elI?.name ? ' (' + elI.name + ')' : ''} — potential duplicate schema`,
|
|
86
|
-
fix_hint: 'Merge schemas or differentiate field sets',
|
|
87
|
-
context: {
|
|
88
|
-
element_type: elJ?.type,
|
|
89
|
-
element_name: elJ?.name,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return violations;
|
|
26
|
+
return nearDuplicates(graph, schemaSimilarity(graph), 'ND-02', 'schema', 'Merge schemas or differentiate field sets');
|
|
95
27
|
}
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Aggregate
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
28
|
export const ND_RULES = [
|
|
100
29
|
{ id: 'ND-01', name: 'FuncNearDuplicate', severity: 'error', evaluate: nd01FuncNearDuplicate, domain: ['FUNC'] },
|
|
101
30
|
{ id: 'ND-02', name: 'SchemaNearDuplicate', severity: 'error', evaluate: nd02SchemaNearDuplicate, domain: ['SCHEMA'] },
|
package/dist/se/ontology.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ export declare const ElementType: z.ZodEnum<{
|
|
|
21
21
|
TEST: "TEST";
|
|
22
22
|
MOD: "MOD";
|
|
23
23
|
SCHEMA: "SCHEMA";
|
|
24
|
-
SESSION: "SESSION";
|
|
25
24
|
CR: "CR";
|
|
26
25
|
MS: "MS";
|
|
27
26
|
}>;
|
|
@@ -38,28 +37,48 @@ export declare const TraceType: z.ZodEnum<{
|
|
|
38
37
|
verify: "verify";
|
|
39
38
|
allocate: "allocate";
|
|
40
39
|
relation: "relation";
|
|
41
|
-
produces: "produces";
|
|
42
40
|
}>;
|
|
43
41
|
export type TraceType = z.infer<typeof TraceType>;
|
|
44
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* REQ kind — 6 values aligned with SysML 2.0 + FMEA (CR-180).
|
|
44
|
+
*
|
|
45
|
+
* CR-SM-266 B: `negative` ENTFAELLT. Ein Verbots-REQ ("das System tut X nie") ist auch eine
|
|
46
|
+
* Anforderung — die Implementierung muss Regeln abfragen oder Sicherungen einbauen, und das
|
|
47
|
+
* ist `functional`. Der Wert hatte NULL Leser (kein Regel-, Gate- oder View-Konsument; anders
|
|
48
|
+
* als risk/mitigation → FM-01..03, pre/postcondition → UC-05/06, non-functional → NFR-01) und
|
|
49
|
+
* haette durch die where-Praedikate am satisfy-Pattern per AUSLASSUNG erstmals Wirkung
|
|
50
|
+
* bekommen: er stand in keiner der vier Listen und waere damit nur noch per FCHAIN erfuellbar
|
|
51
|
+
* gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG — kein
|
|
52
|
+
* Wert ohne Zuordnung, keine Kante, die durch ein Loch in der Aufzaehlung faellt.
|
|
53
|
+
*/
|
|
45
54
|
export declare const ReqKind: z.ZodEnum<{
|
|
46
55
|
functional: "functional";
|
|
47
56
|
"non-functional": "non-functional";
|
|
48
57
|
risk: "risk";
|
|
49
|
-
negative: "negative";
|
|
50
58
|
mitigation: "mitigation";
|
|
51
59
|
precondition: "precondition";
|
|
52
60
|
postcondition: "postcondition";
|
|
53
61
|
}>;
|
|
54
62
|
export type ReqKind = z.infer<typeof ReqKind>;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
/**
|
|
64
|
+
* `kinds` in der Form, die WIRKLICH auf Platte liegt — als Liste.
|
|
65
|
+
*
|
|
66
|
+
* Gefunden beim Migrations-Audit zu CR-SM-266 (2026-08-25), nicht von Hand: drei Graphen der
|
|
67
|
+
* Familie tragen `kinds` als blossen STRING statt als Liste (`graph-view-edit`
|
|
68
|
+
* `REQ-edit-genesis: 'functional'`, dazu gc_test-graphview und graphify), einer davon mit dem
|
|
69
|
+
* Wert `'security'`, den das Enum gar nicht kennt. Ursache ist der Format-E-Weg: dort reist
|
|
70
|
+
* das Feld als `@kinds a,b` und wird erst vom Konsumenten gehoben (graph-api-core, CR-195d).
|
|
71
|
+
*
|
|
72
|
+
* Seit die where-Praedikate `kinds` LESEN (CR-SM-266 B), ist diese Drift tragend: ein
|
|
73
|
+
* `kinds.every(...)` auf einem String wirft, und eine geworfene Exception in `isValidTrace`
|
|
74
|
+
* reisst den ganzen Regellauf statt einen Befund zu melden. Die SSOT muss auch bei kaputten
|
|
75
|
+
* Daten ein URTEIL liefern, keinen Absturz.
|
|
76
|
+
*
|
|
77
|
+
* Normalisieren heisst hier ausdruecklich NICHT raten: `'a,b'` wird zu `['a','b']`, weil genau
|
|
78
|
+
* das die Schreibform ist — ein unbekannter Wert wie `'security'` ueberlebt die Normalisierung
|
|
79
|
+
* und faellt danach am Enum-Vergleich durch. Die Drift wird sichtbar, nicht geheilt.
|
|
80
|
+
*/
|
|
81
|
+
export declare function normalizeReqKinds(raw: unknown): readonly string[];
|
|
63
82
|
/** INCOSE TIAD verification method (only relevant for type=TEST). */
|
|
64
83
|
export declare const VerificationMethod: z.ZodEnum<{
|
|
65
84
|
test: "test";
|
|
@@ -222,19 +241,11 @@ export declare const OntologyElement: z.ZodObject<{
|
|
|
222
241
|
TEST: "TEST";
|
|
223
242
|
MOD: "MOD";
|
|
224
243
|
SCHEMA: "SCHEMA";
|
|
225
|
-
SESSION: "SESSION";
|
|
226
244
|
CR: "CR";
|
|
227
245
|
MS: "MS";
|
|
228
246
|
}>;
|
|
229
247
|
name: z.ZodString;
|
|
230
248
|
description: z.ZodString;
|
|
231
|
-
asil: z.ZodOptional<z.ZodEnum<{
|
|
232
|
-
QM: "QM";
|
|
233
|
-
A: "A";
|
|
234
|
-
B: "B";
|
|
235
|
-
C: "C";
|
|
236
|
-
D: "D";
|
|
237
|
-
}>>;
|
|
238
249
|
method: z.ZodOptional<z.ZodEnum<{
|
|
239
250
|
test: "test";
|
|
240
251
|
inspection: "inspection";
|
|
@@ -245,7 +256,6 @@ export declare const OntologyElement: z.ZodObject<{
|
|
|
245
256
|
functional: "functional";
|
|
246
257
|
"non-functional": "non-functional";
|
|
247
258
|
risk: "risk";
|
|
248
|
-
negative: "negative";
|
|
249
259
|
mitigation: "mitigation";
|
|
250
260
|
precondition: "precondition";
|
|
251
261
|
postcondition: "postcondition";
|
|
@@ -265,12 +275,6 @@ export type OntologyElement = z.infer<typeof OntologyElement>;
|
|
|
265
275
|
* A trace (edge) in the SE ontology graph.
|
|
266
276
|
* `label` provides semantic context for 'relation' edges (e.g. 'derives', 'depends-on').
|
|
267
277
|
*/
|
|
268
|
-
/** Trace category: modeling traces are user-visible, audit traces are internal. */
|
|
269
|
-
export declare const TraceCategory: z.ZodEnum<{
|
|
270
|
-
modeling: "modeling";
|
|
271
|
-
audit: "audit";
|
|
272
|
-
}>;
|
|
273
|
-
export type TraceCategory = z.infer<typeof TraceCategory>;
|
|
274
278
|
export declare const Trace: z.ZodObject<{
|
|
275
279
|
source: z.ZodString;
|
|
276
280
|
target: z.ZodString;
|
|
@@ -281,12 +285,7 @@ export declare const Trace: z.ZodObject<{
|
|
|
281
285
|
verify: "verify";
|
|
282
286
|
allocate: "allocate";
|
|
283
287
|
relation: "relation";
|
|
284
|
-
produces: "produces";
|
|
285
288
|
}>;
|
|
286
|
-
category: z.ZodOptional<z.ZodEnum<{
|
|
287
|
-
modeling: "modeling";
|
|
288
|
-
audit: "audit";
|
|
289
|
-
}>>;
|
|
290
289
|
label: z.ZodOptional<z.ZodString>;
|
|
291
290
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
292
291
|
attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -308,19 +307,11 @@ export declare const OntologyGraph: z.ZodObject<{
|
|
|
308
307
|
TEST: "TEST";
|
|
309
308
|
MOD: "MOD";
|
|
310
309
|
SCHEMA: "SCHEMA";
|
|
311
|
-
SESSION: "SESSION";
|
|
312
310
|
CR: "CR";
|
|
313
311
|
MS: "MS";
|
|
314
312
|
}>;
|
|
315
313
|
name: z.ZodString;
|
|
316
314
|
description: z.ZodString;
|
|
317
|
-
asil: z.ZodOptional<z.ZodEnum<{
|
|
318
|
-
QM: "QM";
|
|
319
|
-
A: "A";
|
|
320
|
-
B: "B";
|
|
321
|
-
C: "C";
|
|
322
|
-
D: "D";
|
|
323
|
-
}>>;
|
|
324
315
|
method: z.ZodOptional<z.ZodEnum<{
|
|
325
316
|
test: "test";
|
|
326
317
|
inspection: "inspection";
|
|
@@ -331,7 +322,6 @@ export declare const OntologyGraph: z.ZodObject<{
|
|
|
331
322
|
functional: "functional";
|
|
332
323
|
"non-functional": "non-functional";
|
|
333
324
|
risk: "risk";
|
|
334
|
-
negative: "negative";
|
|
335
325
|
mitigation: "mitigation";
|
|
336
326
|
precondition: "precondition";
|
|
337
327
|
postcondition: "postcondition";
|
|
@@ -356,12 +346,7 @@ export declare const OntologyGraph: z.ZodObject<{
|
|
|
356
346
|
verify: "verify";
|
|
357
347
|
allocate: "allocate";
|
|
358
348
|
relation: "relation";
|
|
359
|
-
produces: "produces";
|
|
360
349
|
}>;
|
|
361
|
-
category: z.ZodOptional<z.ZodEnum<{
|
|
362
|
-
modeling: "modeling";
|
|
363
|
-
audit: "audit";
|
|
364
|
-
}>>;
|
|
365
350
|
label: z.ZodOptional<z.ZodString>;
|
|
366
351
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
367
352
|
attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -372,7 +357,7 @@ export declare const OntologyGraph: z.ZodObject<{
|
|
|
372
357
|
export type OntologyGraph = z.infer<typeof OntologyGraph>;
|
|
373
358
|
/** Human-readable descriptions for each ElementType. */
|
|
374
359
|
export declare const ELEMENT_DESCRIPTIONS: Record<ElementType, string>;
|
|
375
|
-
/** Element types used in modeling (user-visible). Excludes
|
|
360
|
+
/** Element types used in modeling (user-visible). Excludes the planning type CR. */
|
|
376
361
|
export declare const MODELING_ELEMENT_TYPES: ElementType[];
|
|
377
362
|
export interface AttributeSpec {
|
|
378
363
|
key: string;
|
package/dist/se/ontology.js
CHANGED
|
@@ -13,7 +13,7 @@ import { z } from 'zod/v4';
|
|
|
13
13
|
export const ElementType = z.enum([
|
|
14
14
|
'SYS', 'UC', 'ACTOR', 'FCHAIN',
|
|
15
15
|
'FUNC', 'FLOW', 'REQ', 'TEST',
|
|
16
|
-
'MOD', 'SCHEMA', '
|
|
16
|
+
'MOD', 'SCHEMA', 'CR',
|
|
17
17
|
'MS',
|
|
18
18
|
]);
|
|
19
19
|
/**
|
|
@@ -22,14 +22,49 @@ export const ElementType = z.enum([
|
|
|
22
22
|
* verify=test coverage, allocate=function-to-module, relation=generic link.
|
|
23
23
|
*/
|
|
24
24
|
export const TraceType = z.enum([
|
|
25
|
-
'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation',
|
|
25
|
+
'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation',
|
|
26
26
|
]);
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* REQ kind — 6 values aligned with SysML 2.0 + FMEA (CR-180).
|
|
29
|
+
*
|
|
30
|
+
* CR-SM-266 B: `negative` ENTFAELLT. Ein Verbots-REQ ("das System tut X nie") ist auch eine
|
|
31
|
+
* Anforderung — die Implementierung muss Regeln abfragen oder Sicherungen einbauen, und das
|
|
32
|
+
* ist `functional`. Der Wert hatte NULL Leser (kein Regel-, Gate- oder View-Konsument; anders
|
|
33
|
+
* als risk/mitigation → FM-01..03, pre/postcondition → UC-05/06, non-functional → NFR-01) und
|
|
34
|
+
* haette durch die where-Praedikate am satisfy-Pattern per AUSLASSUNG erstmals Wirkung
|
|
35
|
+
* bekommen: er stand in keiner der vier Listen und waere damit nur noch per FCHAIN erfuellbar
|
|
36
|
+
* gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG — kein
|
|
37
|
+
* Wert ohne Zuordnung, keine Kante, die durch ein Loch in der Aufzaehlung faellt.
|
|
38
|
+
*/
|
|
28
39
|
export const ReqKind = z.enum([
|
|
29
|
-
'functional', 'non-functional', 'risk',
|
|
40
|
+
'functional', 'non-functional', 'risk',
|
|
30
41
|
'mitigation', 'precondition', 'postcondition',
|
|
31
42
|
]);
|
|
32
|
-
|
|
43
|
+
/**
|
|
44
|
+
* `kinds` in der Form, die WIRKLICH auf Platte liegt — als Liste.
|
|
45
|
+
*
|
|
46
|
+
* Gefunden beim Migrations-Audit zu CR-SM-266 (2026-08-25), nicht von Hand: drei Graphen der
|
|
47
|
+
* Familie tragen `kinds` als blossen STRING statt als Liste (`graph-view-edit`
|
|
48
|
+
* `REQ-edit-genesis: 'functional'`, dazu gc_test-graphview und graphify), einer davon mit dem
|
|
49
|
+
* Wert `'security'`, den das Enum gar nicht kennt. Ursache ist der Format-E-Weg: dort reist
|
|
50
|
+
* das Feld als `@kinds a,b` und wird erst vom Konsumenten gehoben (graph-api-core, CR-195d).
|
|
51
|
+
*
|
|
52
|
+
* Seit die where-Praedikate `kinds` LESEN (CR-SM-266 B), ist diese Drift tragend: ein
|
|
53
|
+
* `kinds.every(...)` auf einem String wirft, und eine geworfene Exception in `isValidTrace`
|
|
54
|
+
* reisst den ganzen Regellauf statt einen Befund zu melden. Die SSOT muss auch bei kaputten
|
|
55
|
+
* Daten ein URTEIL liefern, keinen Absturz.
|
|
56
|
+
*
|
|
57
|
+
* Normalisieren heisst hier ausdruecklich NICHT raten: `'a,b'` wird zu `['a','b']`, weil genau
|
|
58
|
+
* das die Schreibform ist — ein unbekannter Wert wie `'security'` ueberlebt die Normalisierung
|
|
59
|
+
* und faellt danach am Enum-Vergleich durch. Die Drift wird sichtbar, nicht geheilt.
|
|
60
|
+
*/
|
|
61
|
+
export function normalizeReqKinds(raw) {
|
|
62
|
+
if (raw == null)
|
|
63
|
+
return [];
|
|
64
|
+
if (Array.isArray(raw))
|
|
65
|
+
return raw.map(k => String(k).trim()).filter(Boolean);
|
|
66
|
+
return String(raw).split(',').map(k => k.trim()).filter(Boolean);
|
|
67
|
+
}
|
|
33
68
|
/** INCOSE TIAD verification method (only relevant for type=TEST). */
|
|
34
69
|
export const VerificationMethod = z.enum(['test', 'inspection', 'analysis', 'demonstration']);
|
|
35
70
|
/** Test execution result (only relevant for type=TEST). */
|
|
@@ -161,7 +196,6 @@ export const OntologyElement = z.object({
|
|
|
161
196
|
type: ElementType,
|
|
162
197
|
name: z.string(),
|
|
163
198
|
description: z.string(),
|
|
164
|
-
asil: AsilLevel.optional(),
|
|
165
199
|
/** INCOSE TIAD verification method — only for TEST elements (CR-057). */
|
|
166
200
|
method: VerificationMethod.optional(),
|
|
167
201
|
/** REQ kinds: multi-valued classification (CR-180). Only for REQ elements. */
|
|
@@ -175,14 +209,24 @@ export const OntologyElement = z.object({
|
|
|
175
209
|
* A trace (edge) in the SE ontology graph.
|
|
176
210
|
* `label` provides semantic context for 'relation' edges (e.g. 'derives', 'depends-on').
|
|
177
211
|
*/
|
|
178
|
-
|
|
179
|
-
|
|
212
|
+
/*
|
|
213
|
+
* CR-SM-266 D5: `TraceCategory` und `Trace.category` ENTFALLEN ersatzlos.
|
|
214
|
+
*
|
|
215
|
+
* Die Unterscheidung modeling/audit trug genau EIN Pattern (`SESSION -produces-> *`), und das
|
|
216
|
+
* ist mit D5 weg. Was blieb, waere ein SCHLUPFLOCH gewesen, kein Vertrag: R-08 und R-18 haben
|
|
217
|
+
* `category === 'audit'` uebersprungen (`filter(t => t.category !== 'audit')`), also haette
|
|
218
|
+
* JEDE Kante mit diesem Attribut die komplette Pattern-Matrix umgangen — die
|
|
219
|
+
* Referenzintegritaet gleich mit. Ein selbstgesetztes Attribut, das eine Strukturpruefung
|
|
220
|
+
* abschaltet, ist genau die Klasse, die CR-SM-263 und CR-SM-257 abgelehnt haben, und hier
|
|
221
|
+
* zusaetzlich der billige Zweitweg aus CR-GC-366: die Kante gaebe es weiterhin, nur ungeprueft.
|
|
222
|
+
* Gemessen: 0 Kanten in allen 9 aktiven Familie-Graphen tragen `category` ueberhaupt — die
|
|
223
|
+
* Entfernung nimmt niemandem etwas weg. Provenance lebt seit CR-GC-347 in
|
|
224
|
+
* `.graphcode/audit.jsonl`, ausserhalb des Graphen.
|
|
225
|
+
*/
|
|
180
226
|
export const Trace = z.object({
|
|
181
227
|
source: z.string(),
|
|
182
228
|
target: z.string(),
|
|
183
229
|
type: TraceType,
|
|
184
|
-
/** 'modeling' (default, user-visible) or 'audit' (internal, e.g. produces traces). Absent = modeling. */
|
|
185
|
-
category: TraceCategory.optional(),
|
|
186
230
|
label: z.string().optional(),
|
|
187
231
|
weight: z.number().default(1),
|
|
188
232
|
attributes: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -208,12 +252,11 @@ export const ELEMENT_DESCRIPTIONS = {
|
|
|
208
252
|
TEST: 'Testfall',
|
|
209
253
|
MOD: 'Modul (SW-Paket oder HW-Baugruppe)',
|
|
210
254
|
SCHEMA: 'Datenschema (Zod)',
|
|
211
|
-
SESSION: 'Audit-Session',
|
|
212
255
|
CR: 'Change Request',
|
|
213
256
|
MS: 'Meilenstein',
|
|
214
257
|
};
|
|
215
|
-
/** Element types used in modeling (user-visible). Excludes
|
|
216
|
-
export const MODELING_ELEMENT_TYPES = ElementType.options.filter(t => t !== '
|
|
258
|
+
/** Element types used in modeling (user-visible). Excludes the planning type CR. */
|
|
259
|
+
export const MODELING_ELEMENT_TYPES = ElementType.options.filter(t => t !== 'CR');
|
|
217
260
|
/**
|
|
218
261
|
* Documented attributes per ElementType.
|
|
219
262
|
* `attributes` is Record<string,unknown> on the schema level, but these are
|
package/dist/se/policy.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare const MetricPolicySchema: z.ZodObject<{
|
|
|
24
24
|
crossingFlows: z.ZodNullable<z.ZodObject<{
|
|
25
25
|
warning: z.ZodNumber;
|
|
26
26
|
}, z.core.$strip>>;
|
|
27
|
+
decompositionBreadth: z.ZodNullable<z.ZodObject<{
|
|
28
|
+
warning: z.ZodNumber;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
boundaryWidth: z.ZodNullable<z.ZodObject<{
|
|
31
|
+
warning: z.ZodNumber;
|
|
32
|
+
}, z.core.$strip>>;
|
|
27
33
|
riskRpn: z.ZodNullable<z.ZodNumber>;
|
|
28
34
|
apTable: z.ZodNullable<z.ZodObject<{
|
|
29
35
|
severityBands: z.ZodArray<z.ZodObject<{
|