@sigloch/contracts 0.7.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/dist/harness/index.d.ts +185 -0
  3. package/dist/harness/index.js +185 -0
  4. package/dist/index.d.ts +13 -0
  5. package/dist/index.js +14 -0
  6. package/dist/se/ao-rules.d.ts +59 -0
  7. package/dist/se/ao-rules.js +341 -0
  8. package/dist/se/conformance-rules.d.ts +64 -0
  9. package/dist/se/conformance-rules.js +364 -0
  10. package/dist/se/cr-quality-rules.d.ts +8 -0
  11. package/dist/se/cr-quality-rules.js +141 -0
  12. package/dist/se/evaluate-all.d.ts +17 -0
  13. package/dist/se/evaluate-all.js +50 -0
  14. package/dist/se/fchain-quality-rules.d.ts +10 -0
  15. package/dist/se/fchain-quality-rules.js +105 -0
  16. package/dist/se/fmea-rules.d.ts +17 -0
  17. package/dist/se/fmea-rules.js +137 -0
  18. package/dist/se/format-e-parser.d.ts +28 -0
  19. package/dist/se/format-e-parser.js +217 -0
  20. package/dist/se/index.d.ts +28 -0
  21. package/dist/se/index.js +28 -0
  22. package/dist/se/meta-model.d.ts +26 -0
  23. package/dist/se/meta-model.js +60 -0
  24. package/dist/se/metric-rules.d.ts +45 -0
  25. package/dist/se/metric-rules.js +208 -0
  26. package/dist/se/near-duplicate-rules.d.ts +44 -0
  27. package/dist/se/near-duplicate-rules.js +106 -0
  28. package/dist/se/ontology.d.ts +327 -0
  29. package/dist/se/ontology.js +216 -0
  30. package/dist/se/quality-rules.d.ts +28 -0
  31. package/dist/se/quality-rules.js +206 -0
  32. package/dist/se/readiness.d.ts +62 -0
  33. package/dist/se/readiness.js +79 -0
  34. package/dist/se/rules.d.ts +159 -0
  35. package/dist/se/rules.js +854 -0
  36. package/dist/se/schema-quality-rules.d.ts +11 -0
  37. package/dist/se/schema-quality-rules.js +73 -0
  38. package/dist/se/semantic-id.d.ts +30 -0
  39. package/dist/se/semantic-id.js +90 -0
  40. package/dist/se/uc-quality-rules.d.ts +13 -0
  41. package/dist/se/uc-quality-rules.js +123 -0
  42. package/dist/se/view-rules.d.ts +11 -0
  43. package/dist/se/view-rules.js +56 -0
  44. package/package.json +51 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CR-121 P3: Architecture Metrics MT-01..MT-03.
3
+ * Module-level metrics computed from graph structure.
4
+ */
5
+ import type { OntologyGraph } from './ontology.js';
6
+ import type { RuleViolation } from './rules.js';
7
+ /**
8
+ * MT-01: Module Instability (CR-165: indirect via allocate-path).
9
+ * I = fan_out / (fan_in + fan_out) > 0.7 → warning.
10
+ * fan_out = traces from FUNCs-in-module pointing to elements OUTSIDE the module.
11
+ * fan_in = traces from OUTSIDE pointing to FUNCs-in-module.
12
+ * Direct MOD→MOD io/compose traces also count.
13
+ */
14
+ export declare function mt01Instability(graph: OntologyGraph): RuleViolation[];
15
+ /**
16
+ * MT-02: LCOM4 (Lack of Cohesion — component count).
17
+ * MOD with allocated FUNCs that share no common io/satisfy targets → cohesion problem.
18
+ * Components > 1 → info.
19
+ */
20
+ export declare function mt02Lcom4(graph: OntologyGraph): RuleViolation[];
21
+ /**
22
+ * MT-03: Allocation Cohesion (CR-191: reformulated).
23
+ * cohesion = internal_flows / (internal_flows + external_flows).
24
+ * internal_flows = io traces between FUNCs within the same module.
25
+ * external_flows = io traces crossing module boundary (one end inside, one outside).
26
+ * If external_flows === 0 → cohesion = 100% → OK.
27
+ */
28
+ export declare function mt03AllocationCohesion(graph: OntologyGraph): RuleViolation[];
29
+ export declare const MT_RULES: readonly [{
30
+ readonly id: "MT-01";
31
+ readonly name: "Module instability";
32
+ readonly severity: "warning";
33
+ readonly evaluate: typeof mt01Instability;
34
+ }, {
35
+ readonly id: "MT-02";
36
+ readonly name: "Module cohesion (LCOM4)";
37
+ readonly severity: "info";
38
+ readonly evaluate: typeof mt02Lcom4;
39
+ }, {
40
+ readonly id: "MT-03";
41
+ readonly name: "Allocation cohesion";
42
+ readonly severity: "info";
43
+ readonly evaluate: typeof mt03AllocationCohesion;
44
+ }];
45
+ export declare function evaluateMTRules(graph: OntologyGraph): RuleViolation[];
@@ -0,0 +1,208 @@
1
+ const INSTABILITY_THRESHOLD = 0.7;
2
+ /**
3
+ * MT-01: Module Instability (CR-165: indirect via allocate-path).
4
+ * I = fan_out / (fan_in + fan_out) > 0.7 → warning.
5
+ * fan_out = traces from FUNCs-in-module pointing to elements OUTSIDE the module.
6
+ * fan_in = traces from OUTSIDE pointing to FUNCs-in-module.
7
+ * Direct MOD→MOD io/compose traces also count.
8
+ */
9
+ export function mt01Instability(graph) {
10
+ const violations = [];
11
+ const mods = graph.elements.filter(e => e.type === 'MOD');
12
+ for (const mod of mods) {
13
+ // FUNCs allocated to this module
14
+ const modFuncIds = new Set(graph.traces
15
+ .filter(t => t.type === 'allocate' && t.target === mod.id)
16
+ .map(t => t.source));
17
+ // Direct MOD-level traces (MOD→MOD io/compose)
18
+ const directOut = graph.traces.filter(t => t.source === mod.id && (t.type === 'io' || t.type === 'compose') && t.target !== mod.id).length;
19
+ const directIn = graph.traces.filter(t => t.target === mod.id && (t.type === 'io' || t.type === 'compose' || t.type === 'allocate')).length;
20
+ // Indirect: traces from module FUNCs to outside elements (and vice versa)
21
+ let indirectOut = 0;
22
+ let indirectIn = 0;
23
+ for (const t of graph.traces) {
24
+ if (t.type === 'allocate')
25
+ continue; // allocate itself doesn't count as coupling
26
+ if (modFuncIds.has(t.source) && !modFuncIds.has(t.target) && t.target !== mod.id) {
27
+ indirectOut++;
28
+ }
29
+ if (modFuncIds.has(t.target) && !modFuncIds.has(t.source) && t.source !== mod.id) {
30
+ indirectIn++;
31
+ }
32
+ }
33
+ const fanOut = directOut + indirectOut;
34
+ const fanIn = directIn + indirectIn;
35
+ if (fanIn + fanOut === 0)
36
+ continue;
37
+ const instability = fanOut / (fanIn + fanOut);
38
+ if (instability > INSTABILITY_THRESHOLD) {
39
+ violations.push({
40
+ rule_id: 'MT-01',
41
+ severity: 'warning',
42
+ element_id: mod.id,
43
+ message: `${mod.name} has instability ${Math.round(instability * 100)}% (>${INSTABILITY_THRESHOLD * 100}%). fan_in=${fanIn}, fan_out=${fanOut}`,
44
+ });
45
+ }
46
+ }
47
+ return violations;
48
+ }
49
+ /**
50
+ * MT-02: LCOM4 (Lack of Cohesion — component count).
51
+ * MOD with allocated FUNCs that share no common io/satisfy targets → cohesion problem.
52
+ * Components > 1 → info.
53
+ */
54
+ export function mt02Lcom4(graph) {
55
+ const violations = [];
56
+ const mods = graph.elements.filter(e => e.type === 'MOD');
57
+ for (const mod of mods) {
58
+ // Find all FUNCs allocated to this MOD
59
+ const allocTraces = graph.traces.filter(t => t.type === 'allocate' && t.target === mod.id);
60
+ const funcIds = allocTraces.map(t => t.source);
61
+ if (funcIds.length < 2)
62
+ continue;
63
+ // Build adjacency: two FUNCs are connected if they share a common io/satisfy target
64
+ const funcTargets = new Map();
65
+ for (const fid of funcIds) {
66
+ const targets = new Set();
67
+ for (const t of graph.traces) {
68
+ if (t.source === fid && (t.type === 'io' || t.type === 'satisfy')) {
69
+ targets.add(t.target);
70
+ }
71
+ }
72
+ funcTargets.set(fid, targets);
73
+ }
74
+ // Count connected components via union-find
75
+ const parent = new Map();
76
+ for (const fid of funcIds)
77
+ parent.set(fid, fid);
78
+ function find(x) {
79
+ while (parent.get(x) !== x) {
80
+ x = parent.get(x);
81
+ }
82
+ return x;
83
+ }
84
+ function union(a, b) {
85
+ const ra = find(a), rb = find(b);
86
+ if (ra !== rb)
87
+ parent.set(ra, rb);
88
+ }
89
+ for (let i = 0; i < funcIds.length; i++) {
90
+ for (let j = i + 1; j < funcIds.length; j++) {
91
+ const ti = funcTargets.get(funcIds[i]);
92
+ const tj = funcTargets.get(funcIds[j]);
93
+ // Check if they share any target
94
+ for (const t of ti) {
95
+ if (tj.has(t)) {
96
+ union(funcIds[i], funcIds[j]);
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ // CR-165/CR-171: FUNCs sharing the same FLOW (via io in either direction) are connected
103
+ const funcIdSet = new Set(funcIds);
104
+ const flowToFuncs = new Map();
105
+ for (const t of graph.traces) {
106
+ if (t.type !== 'io')
107
+ continue;
108
+ // FLOW→FUNC direction
109
+ if (funcIdSet.has(t.target)) {
110
+ const srcEl = graph.elements.find(e => e.id === t.source);
111
+ if (srcEl?.type === 'FLOW') {
112
+ if (!flowToFuncs.has(t.source))
113
+ flowToFuncs.set(t.source, new Set());
114
+ flowToFuncs.get(t.source).add(t.target);
115
+ }
116
+ }
117
+ // FUNC→FLOW direction (CR-171)
118
+ if (funcIdSet.has(t.source)) {
119
+ const tgtEl = graph.elements.find(e => e.id === t.target);
120
+ if (tgtEl?.type === 'FLOW') {
121
+ if (!flowToFuncs.has(t.target))
122
+ flowToFuncs.set(t.target, new Set());
123
+ flowToFuncs.get(t.target).add(t.source);
124
+ }
125
+ }
126
+ }
127
+ for (const funcsInFlow of flowToFuncs.values()) {
128
+ const arr = [...funcsInFlow];
129
+ for (let i = 1; i < arr.length; i++)
130
+ union(arr[0], arr[i]);
131
+ }
132
+ const roots = new Set(funcIds.map(find));
133
+ const lcom4 = roots.size;
134
+ if (lcom4 >= 4 && lcom4 <= 5) {
135
+ violations.push({
136
+ rule_id: 'MT-02',
137
+ severity: 'info',
138
+ element_id: mod.id,
139
+ message: `${mod.name} has LCOM4=${lcom4} (${funcIds.length} FUNCs in ${lcom4} disconnected groups)`,
140
+ });
141
+ }
142
+ else if (lcom4 > 5) {
143
+ violations.push({
144
+ rule_id: 'MT-02',
145
+ severity: 'warning',
146
+ element_id: mod.id,
147
+ message: `${mod.name} has LCOM4=${lcom4} (${funcIds.length} FUNCs in ${lcom4} disconnected groups)`,
148
+ });
149
+ }
150
+ }
151
+ return violations;
152
+ }
153
+ /**
154
+ * MT-03: Allocation Cohesion (CR-191: reformulated).
155
+ * cohesion = internal_flows / (internal_flows + external_flows).
156
+ * internal_flows = io traces between FUNCs within the same module.
157
+ * external_flows = io traces crossing module boundary (one end inside, one outside).
158
+ * If external_flows === 0 → cohesion = 100% → OK.
159
+ */
160
+ export function mt03AllocationCohesion(graph) {
161
+ const violations = [];
162
+ const mods = graph.elements.filter(e => e.type === 'MOD');
163
+ const COHESION_THRESHOLD = 0.8;
164
+ for (const mod of mods) {
165
+ const allocTraces = graph.traces.filter(t => t.type === 'allocate' && t.target === mod.id);
166
+ const funcIds = new Set(allocTraces.map(t => t.source));
167
+ if (funcIds.size < 2)
168
+ continue;
169
+ let internalFlows = 0;
170
+ let externalFlows = 0;
171
+ for (const t of graph.traces) {
172
+ if (t.type !== 'io')
173
+ continue;
174
+ const srcIn = funcIds.has(t.source);
175
+ const tgtIn = funcIds.has(t.target);
176
+ if (srcIn && tgtIn) {
177
+ internalFlows++;
178
+ }
179
+ else if (srcIn || tgtIn) {
180
+ externalFlows++;
181
+ }
182
+ }
183
+ // No external flows → cohesion = 100% → OK
184
+ if (externalFlows === 0)
185
+ continue;
186
+ const total = internalFlows + externalFlows;
187
+ if (total === 0)
188
+ continue;
189
+ const cohesion = internalFlows / total;
190
+ if (cohesion < COHESION_THRESHOLD) {
191
+ violations.push({
192
+ rule_id: 'MT-03',
193
+ severity: 'info',
194
+ element_id: mod.id,
195
+ message: `${mod.name} allocation cohesion ${Math.round(cohesion * 100)}% (<${COHESION_THRESHOLD * 100}%). internal=${internalFlows}, external=${externalFlows}`,
196
+ });
197
+ }
198
+ }
199
+ return violations;
200
+ }
201
+ export const MT_RULES = [
202
+ { id: 'MT-01', name: 'Module instability', severity: 'warning', evaluate: mt01Instability },
203
+ { id: 'MT-02', name: 'Module cohesion (LCOM4)', severity: 'info', evaluate: mt02Lcom4 },
204
+ { id: 'MT-03', name: 'Allocation cohesion', severity: 'info', evaluate: mt03AllocationCohesion },
205
+ ];
206
+ export function evaluateMTRules(graph) {
207
+ return MT_RULES.flatMap(r => r.evaluate(graph));
208
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CR-121 P2: Near-Duplicate Detection for FUNC and SCHEMA elements.
3
+ * Uses pre-injected similarity matrices (same pattern as BQ-04).
4
+ */
5
+ import type { OntologyGraph } from './ontology.js';
6
+ 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. */
16
+ 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. */
31
+ export declare function nd02SchemaNearDuplicate(graph: OntologyGraph): RuleViolation[];
32
+ export declare const ND_RULES: readonly [{
33
+ readonly id: "ND-01";
34
+ readonly name: "FuncNearDuplicate";
35
+ readonly severity: "error";
36
+ readonly evaluate: typeof nd01FuncNearDuplicate;
37
+ }, {
38
+ readonly id: "ND-02";
39
+ readonly name: "SchemaNearDuplicate";
40
+ readonly severity: "error";
41
+ readonly evaluate: typeof nd02SchemaNearDuplicate;
42
+ }];
43
+ /** Evaluate all near-duplicate rules. */
44
+ export declare function evaluateNDRules(graph: OntologyGraph): RuleViolation[];
@@ -0,0 +1,106 @@
1
+ const ND_SIMILARITY_THRESHOLD = 0.85;
2
+ // ---------------------------------------------------------------------------
3
+ // ND-01: FUNC near-duplicates
4
+ // ---------------------------------------------------------------------------
5
+ let _nd01Matrix = null;
6
+ /**
7
+ * Inject pre-computed similarity matrix for FUNC elements.
8
+ * Similarity = 0.35 × descr_jaccard + 0.25 × verb_match + 0.25 × io_topology + 0.15 × req_overlap
9
+ */
10
+ export function setND01SimilarityMatrix(data) {
11
+ _nd01Matrix = data;
12
+ }
13
+ /** ND-01: FUNC pairs with similarity >= 0.85 → error. */
14
+ export function nd01FuncNearDuplicate(graph) {
15
+ if (!_nd01Matrix)
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;
46
+ }
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. */
63
+ export function nd02SchemaNearDuplicate(graph) {
64
+ if (!_nd02Matrix)
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;
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Aggregate
98
+ // ---------------------------------------------------------------------------
99
+ export const ND_RULES = [
100
+ { id: 'ND-01', name: 'FuncNearDuplicate', severity: 'error', evaluate: nd01FuncNearDuplicate },
101
+ { id: 'ND-02', name: 'SchemaNearDuplicate', severity: 'error', evaluate: nd02SchemaNearDuplicate },
102
+ ];
103
+ /** Evaluate all near-duplicate rules. */
104
+ export function evaluateNDRules(graph) {
105
+ return ND_RULES.flatMap(r => r.evaluate(graph));
106
+ }