@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.
- package/LICENSE +21 -0
- package/dist/harness/index.d.ts +185 -0
- package/dist/harness/index.js +185 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/se/ao-rules.d.ts +59 -0
- package/dist/se/ao-rules.js +341 -0
- package/dist/se/conformance-rules.d.ts +64 -0
- package/dist/se/conformance-rules.js +364 -0
- package/dist/se/cr-quality-rules.d.ts +8 -0
- package/dist/se/cr-quality-rules.js +141 -0
- package/dist/se/evaluate-all.d.ts +17 -0
- package/dist/se/evaluate-all.js +50 -0
- package/dist/se/fchain-quality-rules.d.ts +10 -0
- package/dist/se/fchain-quality-rules.js +105 -0
- package/dist/se/fmea-rules.d.ts +17 -0
- package/dist/se/fmea-rules.js +137 -0
- package/dist/se/format-e-parser.d.ts +28 -0
- package/dist/se/format-e-parser.js +217 -0
- package/dist/se/index.d.ts +28 -0
- package/dist/se/index.js +28 -0
- package/dist/se/meta-model.d.ts +26 -0
- package/dist/se/meta-model.js +60 -0
- package/dist/se/metric-rules.d.ts +45 -0
- package/dist/se/metric-rules.js +208 -0
- package/dist/se/near-duplicate-rules.d.ts +44 -0
- package/dist/se/near-duplicate-rules.js +106 -0
- package/dist/se/ontology.d.ts +327 -0
- package/dist/se/ontology.js +216 -0
- package/dist/se/quality-rules.d.ts +28 -0
- package/dist/se/quality-rules.js +206 -0
- package/dist/se/readiness.d.ts +62 -0
- package/dist/se/readiness.js +79 -0
- package/dist/se/rules.d.ts +159 -0
- package/dist/se/rules.js +854 -0
- package/dist/se/schema-quality-rules.d.ts +11 -0
- package/dist/se/schema-quality-rules.js +73 -0
- package/dist/se/semantic-id.d.ts +30 -0
- package/dist/se/semantic-id.js +90 -0
- package/dist/se/uc-quality-rules.d.ts +13 -0
- package/dist/se/uc-quality-rules.js +123 -0
- package/dist/se/view-rules.d.ts +11 -0
- package/dist/se/view-rules.js +56 -0
- package/package.json +51 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { getND02SimilarityMatrix } from './near-duplicate-rules.js';
|
|
2
|
+
const SCHEMA_OVERLAP_THRESHOLD = 0.5;
|
|
3
|
+
/**
|
|
4
|
+
* AO-D01: Relay Node Detection.
|
|
5
|
+
* A FUNC is a relay if it (a) satisfies no REQ, (b) has >=2 outgoing io to FUNCs,
|
|
6
|
+
* and (c) target FUNCs share SCHEMA overlap >= 0.5 (via ND-02 matrix, skipped if unavailable).
|
|
7
|
+
*/
|
|
8
|
+
export function aoD01RelayNode(graph) {
|
|
9
|
+
const violations = [];
|
|
10
|
+
const funcs = graph.elements.filter(e => e.type === 'FUNC');
|
|
11
|
+
const funcIdSet = new Set(funcs.map(f => f.id));
|
|
12
|
+
for (const func of funcs) {
|
|
13
|
+
// (a) No satisfy traces (FUNC as source) connecting to any REQ
|
|
14
|
+
const hasSatisfy = graph.traces.some(t => t.type === 'satisfy' &&
|
|
15
|
+
t.source === func.id && graph.elements.some(e => e.id === t.target && e.type === 'REQ'));
|
|
16
|
+
if (hasSatisfy)
|
|
17
|
+
continue;
|
|
18
|
+
// (b) >=2 outgoing io traces to other FUNCs
|
|
19
|
+
const ioTargets = graph.traces
|
|
20
|
+
.filter(t => t.type === 'io' && t.source === func.id && funcIdSet.has(t.target))
|
|
21
|
+
.map(t => t.target);
|
|
22
|
+
if (ioTargets.length < 2)
|
|
23
|
+
continue;
|
|
24
|
+
// (c) Target FUNCs share SCHEMA overlap >= 0.5 (skip check if no matrix)
|
|
25
|
+
if (!passesSchemaOverlap(graph, ioTargets))
|
|
26
|
+
continue;
|
|
27
|
+
const el = graph.elements.find(e => e.id === func.id);
|
|
28
|
+
violations.push({
|
|
29
|
+
rule_id: 'AO-D01',
|
|
30
|
+
severity: 'info',
|
|
31
|
+
element_id: func.id,
|
|
32
|
+
message: `${func.id}${el?.name ? ' (' + el.name + ')' : ''} is a relay node: no REQ satisfied, ${ioTargets.length} io targets`,
|
|
33
|
+
fix_hint: 'Consider eliminating relay by connecting consumers directly to producer',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return violations;
|
|
37
|
+
}
|
|
38
|
+
/** CR-165: Resolve FUNC→SCHEMA via FUNC→FLOW(io) + FLOW→SCHEMA(relation). */
|
|
39
|
+
function funcToSchemas(graph, funcId) {
|
|
40
|
+
const schemas = new Set();
|
|
41
|
+
// Find FLOWs connected to this FUNC via io (FUNC→FLOW or FLOW→FUNC)
|
|
42
|
+
const flowIds = new Set();
|
|
43
|
+
for (const t of graph.traces) {
|
|
44
|
+
if (t.type === 'io') {
|
|
45
|
+
if (t.source === funcId && graph.elements.some(e => e.id === t.target && e.type === 'FLOW')) {
|
|
46
|
+
flowIds.add(t.target);
|
|
47
|
+
}
|
|
48
|
+
if (t.target === funcId && graph.elements.some(e => e.id === t.source && e.type === 'FLOW')) {
|
|
49
|
+
flowIds.add(t.source);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Find SCHEMAs connected to those FLOWs via relation
|
|
54
|
+
for (const t of graph.traces) {
|
|
55
|
+
if (t.type === 'relation' && flowIds.has(t.source) && graph.elements.some(e => e.id === t.target && e.type === 'SCHEMA')) {
|
|
56
|
+
schemas.add(t.target);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return schemas;
|
|
60
|
+
}
|
|
61
|
+
/** Check if any pair of target FUNCs shares SCHEMA overlap via FLOW→SCHEMA(relation) + ND-02 matrix. */
|
|
62
|
+
function passesSchemaOverlap(graph, targetFuncIds) {
|
|
63
|
+
const nd02 = getND02SimilarityMatrix();
|
|
64
|
+
if (!nd02)
|
|
65
|
+
return true; // no matrix → skip check, assume pass
|
|
66
|
+
// For each target FUNC, collect connected SCHEMA ids via FLOW→SCHEMA(relation)
|
|
67
|
+
const funcSchemasMap = new Map();
|
|
68
|
+
for (const fid of targetFuncIds) {
|
|
69
|
+
funcSchemasMap.set(fid, funcToSchemas(graph, fid));
|
|
70
|
+
}
|
|
71
|
+
// Check pairwise: any pair of target FUNCs with overlapping SCHEMAs (similarity >= threshold)
|
|
72
|
+
const { schemaIds, matrix } = nd02;
|
|
73
|
+
const idxOf = new Map(schemaIds.map((id, i) => [id, i]));
|
|
74
|
+
for (let i = 0; i < targetFuncIds.length; i++) {
|
|
75
|
+
for (let j = i + 1; j < targetFuncIds.length; j++) {
|
|
76
|
+
const si = funcSchemasMap.get(targetFuncIds[i]);
|
|
77
|
+
const sj = funcSchemasMap.get(targetFuncIds[j]);
|
|
78
|
+
for (const a of si) {
|
|
79
|
+
for (const b of sj) {
|
|
80
|
+
const ai = idxOf.get(a), bi = idxOf.get(b);
|
|
81
|
+
if (ai !== undefined && bi !== undefined && matrix[ai][bi] >= SCHEMA_OVERLAP_THRESHOLD)
|
|
82
|
+
return true;
|
|
83
|
+
if (a === b)
|
|
84
|
+
return true; // same SCHEMA = 100% overlap
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* AO-D03: Duplicate Path Detection.
|
|
93
|
+
* FUNC A sends io to both B and C, where B and C connect to the same SCHEMA elements.
|
|
94
|
+
*/
|
|
95
|
+
export function aoD03DuplicatePath(graph) {
|
|
96
|
+
const violations = [];
|
|
97
|
+
const funcs = graph.elements.filter(e => e.type === 'FUNC');
|
|
98
|
+
const funcIdSet = new Set(funcs.map(f => f.id));
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
// Pre-compute: FUNC → set of SCHEMA targets via FLOW→SCHEMA(relation) (CR-165)
|
|
101
|
+
const funcToSchemasMap = new Map();
|
|
102
|
+
for (const f of funcs) {
|
|
103
|
+
funcToSchemasMap.set(f.id, funcToSchemas(graph, f.id));
|
|
104
|
+
}
|
|
105
|
+
for (const a of funcs) {
|
|
106
|
+
const ioTargets = graph.traces
|
|
107
|
+
.filter(t => t.type === 'io' && t.source === a.id && funcIdSet.has(t.target))
|
|
108
|
+
.map(t => t.target);
|
|
109
|
+
if (ioTargets.length < 2)
|
|
110
|
+
continue;
|
|
111
|
+
for (let i = 0; i < ioTargets.length; i++) {
|
|
112
|
+
for (let j = i + 1; j < ioTargets.length; j++) {
|
|
113
|
+
const b = ioTargets[i], c = ioTargets[j];
|
|
114
|
+
const key = [a.id, b, c].sort().join(':');
|
|
115
|
+
if (seen.has(key))
|
|
116
|
+
continue;
|
|
117
|
+
const sb = funcToSchemasMap.get(b) ?? new Set();
|
|
118
|
+
const sc = funcToSchemasMap.get(c) ?? new Set();
|
|
119
|
+
const shared = [...sb].filter(s => sc.has(s));
|
|
120
|
+
if (shared.length === 0)
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(key);
|
|
123
|
+
violations.push({
|
|
124
|
+
rule_id: 'AO-D03',
|
|
125
|
+
severity: 'info',
|
|
126
|
+
element_id: a.id,
|
|
127
|
+
message: `${a.id} sends io to ${b} and ${c} which share ${shared.length} SCHEMA target(s): ${shared.join(', ')}`,
|
|
128
|
+
fix_hint: 'Consider introducing a mediator or unifying the data paths',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return violations;
|
|
134
|
+
}
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// CR-01: Crossing Flow Count (CR-191)
|
|
137
|
+
// For each module pair (A, B): count io paths FUNC_A → FUNC_B
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
export function cr01CrossingFlowCount(graph) {
|
|
140
|
+
const violations = [];
|
|
141
|
+
const mods = graph.elements.filter(e => e.type === 'MOD');
|
|
142
|
+
// Build MOD → Set<FUNC-id> mapping
|
|
143
|
+
const modFuncs = new Map();
|
|
144
|
+
for (const mod of mods) {
|
|
145
|
+
const funcIds = new Set(graph.traces.filter(t => t.type === 'allocate' && t.target === mod.id).map(t => t.source));
|
|
146
|
+
modFuncs.set(mod.id, funcIds);
|
|
147
|
+
}
|
|
148
|
+
// Count crossings per module pair
|
|
149
|
+
const pairCounts = new Map();
|
|
150
|
+
for (const t of graph.traces) {
|
|
151
|
+
if (t.type !== 'io')
|
|
152
|
+
continue;
|
|
153
|
+
let srcMod;
|
|
154
|
+
let tgtMod;
|
|
155
|
+
for (const [mid, fids] of modFuncs) {
|
|
156
|
+
if (fids.has(t.source))
|
|
157
|
+
srcMod = mid;
|
|
158
|
+
if (fids.has(t.target))
|
|
159
|
+
tgtMod = mid;
|
|
160
|
+
}
|
|
161
|
+
if (!srcMod || !tgtMod || srcMod === tgtMod)
|
|
162
|
+
continue;
|
|
163
|
+
const key = [srcMod, tgtMod].sort().join('::');
|
|
164
|
+
if (!pairCounts.has(key))
|
|
165
|
+
pairCounts.set(key, { modA: srcMod, modB: tgtMod, count: 0 });
|
|
166
|
+
pairCounts.get(key).count++;
|
|
167
|
+
}
|
|
168
|
+
for (const { modA, modB, count } of pairCounts.values()) {
|
|
169
|
+
if (count <= 0)
|
|
170
|
+
continue;
|
|
171
|
+
const severity = count > 2 ? 'warning' : 'info';
|
|
172
|
+
violations.push({
|
|
173
|
+
rule_id: 'CR-01',
|
|
174
|
+
severity,
|
|
175
|
+
element_id: modA,
|
|
176
|
+
message: `${modA} ↔ ${modB}: ${count} crossing io flow(s)`,
|
|
177
|
+
fix_hint: count > 2 ? 'Reduce coupling between modules or introduce a mediator' : 'Consider if coupling is intentional',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return violations;
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// RT-01: Physical boundary integrity (CR-191)
|
|
184
|
+
// FUNCs must not be allocated directly to physical MODs — use logical sub-modules.
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
export function rt01PhysicalBoundaryIntegrity(graph) {
|
|
187
|
+
const physicalMods = graph.elements.filter(e => e.type === 'MOD' && e.attributes?.kind === 'physical');
|
|
188
|
+
return graph.traces
|
|
189
|
+
.filter(t => t.type === 'allocate' && physicalMods.some(m => m.id === t.target))
|
|
190
|
+
.filter(t => graph.elements.some(e => e.id === t.source && e.type === 'FUNC'))
|
|
191
|
+
.map(t => ({
|
|
192
|
+
rule_id: 'RT-01',
|
|
193
|
+
severity: 'error',
|
|
194
|
+
element_id: t.source,
|
|
195
|
+
message: `${t.source} allocated directly to physical module ${t.target} — must use logical sub-module`,
|
|
196
|
+
fix_hint: 'Create a logical MOD inside the physical MOD and allocate the FUNC there',
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// PH-01: Physical MOD completeness (CR-191)
|
|
201
|
+
// Physical MODs should have at least one logical sub-module via compose trace.
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
export function ph01PhysicalModCompleteness(graph) {
|
|
204
|
+
const physicalMods = graph.elements.filter(e => e.type === 'MOD' && e.attributes?.kind === 'physical');
|
|
205
|
+
return physicalMods
|
|
206
|
+
.filter(pm => !graph.traces.some(t => t.source === pm.id && t.type === 'compose' &&
|
|
207
|
+
graph.elements.some(e => e.id === t.target && e.type === 'MOD')))
|
|
208
|
+
.map(pm => ({
|
|
209
|
+
rule_id: 'PH-01',
|
|
210
|
+
severity: 'info',
|
|
211
|
+
element_id: pm.id,
|
|
212
|
+
message: `${pm.id} (physical) has no logical sub-modules`,
|
|
213
|
+
fix_hint: 'Add at least one logical MOD via compose trace',
|
|
214
|
+
context: { element_type: pm.type, element_name: pm.name },
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// CA-01: Capability allocation check (CR-191)
|
|
219
|
+
// FUNC @requires must be satisfied by the physical MOD's @capability.
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
export function ca01CapabilityAllocation(graph) {
|
|
222
|
+
const violations = [];
|
|
223
|
+
const funcsWithRequires = graph.elements.filter(e => e.type === 'FUNC' && e.attributes?.requires);
|
|
224
|
+
for (const func of funcsWithRequires) {
|
|
225
|
+
const requires = (Array.isArray(func.attributes.requires)
|
|
226
|
+
? func.attributes.requires
|
|
227
|
+
: [func.attributes.requires]);
|
|
228
|
+
// Find logical MOD via allocate
|
|
229
|
+
const allocTrace = graph.traces.find(t => t.source === func.id && t.type === 'allocate');
|
|
230
|
+
if (!allocTrace)
|
|
231
|
+
continue;
|
|
232
|
+
const logicalMod = graph.elements.find(e => e.id === allocTrace.target && e.type === 'MOD');
|
|
233
|
+
if (!logicalMod)
|
|
234
|
+
continue;
|
|
235
|
+
// Find physical parent MOD (compose source → logical MOD target)
|
|
236
|
+
const physParentTrace = graph.traces.find(t => t.type === 'compose' && t.target === logicalMod.id &&
|
|
237
|
+
graph.elements.some(e => e.id === t.source && e.type === 'MOD' && e.attributes?.kind === 'physical'));
|
|
238
|
+
if (!physParentTrace)
|
|
239
|
+
continue;
|
|
240
|
+
const physMod = graph.elements.find(e => e.id === physParentTrace.source);
|
|
241
|
+
const capabilities = (Array.isArray(physMod.attributes?.capability)
|
|
242
|
+
? physMod.attributes.capability
|
|
243
|
+
: physMod.attributes?.capability ? [physMod.attributes.capability] : []);
|
|
244
|
+
const missing = requires.filter(r => !capabilities.includes(r));
|
|
245
|
+
if (missing.length > 0) {
|
|
246
|
+
violations.push({
|
|
247
|
+
rule_id: 'CA-01',
|
|
248
|
+
severity: 'error',
|
|
249
|
+
element_id: func.id,
|
|
250
|
+
message: `${func.id} requires [${missing.join(', ')}] but ${physMod.id} only provides [${capabilities.join(', ')}]`,
|
|
251
|
+
fix_hint: `Add missing capabilities to ${physMod.id} or move ${func.id} to a capable module`,
|
|
252
|
+
context: { element_type: func.type, element_name: func.name, parent_module: physMod.id },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return violations;
|
|
257
|
+
}
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// IO-01: Cross-module IO completeness (CR-192)
|
|
260
|
+
// For each FCHAIN, FUNCs in different MODs must have a FLOW path between them.
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
export function io01CrossModuleCompleteness(graph) {
|
|
263
|
+
const violations = [];
|
|
264
|
+
const fchains = graph.elements.filter(e => e.type === 'FCHAIN');
|
|
265
|
+
for (const fc of fchains) {
|
|
266
|
+
// Get FUNCs in this FCHAIN via compose
|
|
267
|
+
const funcIds = graph.traces
|
|
268
|
+
.filter(t => t.source === fc.id && t.type === 'compose')
|
|
269
|
+
.map(t => t.target)
|
|
270
|
+
.filter(id => graph.elements.some(e => e.id === id && e.type === 'FUNC'));
|
|
271
|
+
if (funcIds.length < 2)
|
|
272
|
+
continue;
|
|
273
|
+
// Map FUNC→MOD via allocate
|
|
274
|
+
const funcToMod = new Map();
|
|
275
|
+
for (const fid of funcIds) {
|
|
276
|
+
const allocTrace = graph.traces.find(t => t.source === fid && t.type === 'allocate');
|
|
277
|
+
if (allocTrace)
|
|
278
|
+
funcToMod.set(fid, allocTrace.target);
|
|
279
|
+
}
|
|
280
|
+
// For each pair of FUNCs in different MODs, check for FLOW path
|
|
281
|
+
const checked = new Set();
|
|
282
|
+
for (const fA of funcIds) {
|
|
283
|
+
for (const fB of funcIds) {
|
|
284
|
+
if (fA >= fB)
|
|
285
|
+
continue;
|
|
286
|
+
const modA = funcToMod.get(fA);
|
|
287
|
+
const modB = funcToMod.get(fB);
|
|
288
|
+
if (!modA || !modB || modA === modB)
|
|
289
|
+
continue;
|
|
290
|
+
const pairKey = `${fA}:${fB}`;
|
|
291
|
+
if (checked.has(pairKey))
|
|
292
|
+
continue;
|
|
293
|
+
checked.add(pairKey);
|
|
294
|
+
// Check if there's a FLOW path: fA→io→FLOW→io→fB or fB→io→FLOW→io→fA
|
|
295
|
+
const hasFlow = graph.traces.some(t1 => {
|
|
296
|
+
if (t1.type !== 'io')
|
|
297
|
+
return false;
|
|
298
|
+
if (t1.source !== fA && t1.source !== fB)
|
|
299
|
+
return false;
|
|
300
|
+
const flowId = t1.target;
|
|
301
|
+
const flowEl = graph.elements.find(e => e.id === flowId && e.type === 'FLOW');
|
|
302
|
+
if (!flowEl)
|
|
303
|
+
return false;
|
|
304
|
+
const otherFunc = t1.source === fA ? fB : fA;
|
|
305
|
+
return graph.traces.some(t2 => t2.type === 'io' &&
|
|
306
|
+
((t2.source === flowId && t2.target === otherFunc) ||
|
|
307
|
+
(t2.target === flowId && t2.source === otherFunc)));
|
|
308
|
+
});
|
|
309
|
+
if (!hasFlow) {
|
|
310
|
+
const elA = graph.elements.find(e => e.id === fA);
|
|
311
|
+
const elB = graph.elements.find(e => e.id === fB);
|
|
312
|
+
violations.push({
|
|
313
|
+
rule_id: 'IO-01',
|
|
314
|
+
severity: 'warning',
|
|
315
|
+
element_id: fA,
|
|
316
|
+
message: `${fA} (${modA}) and ${fB} (${modB}) in FCHAIN ${fc.id} have no IO path — missing FLOW?`,
|
|
317
|
+
fix_hint: 'Add a FLOW element with io traces between these cross-module functions',
|
|
318
|
+
context: {
|
|
319
|
+
element_type: 'FUNC',
|
|
320
|
+
element_name: elA?.name ?? fA,
|
|
321
|
+
candidate_targets: elB ? [{ id: elB.id, type: elB.type, name: elB.name }] : undefined,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return violations;
|
|
329
|
+
}
|
|
330
|
+
export const AO_RULES = [
|
|
331
|
+
{ id: 'AO-D01', name: 'RelayNodeDetection', severity: 'info', evaluate: aoD01RelayNode },
|
|
332
|
+
{ id: 'AO-D03', name: 'DuplicatePathDetection', severity: 'info', evaluate: aoD03DuplicatePath },
|
|
333
|
+
{ id: 'CR-01', name: 'CrossingFlowCount', severity: 'warning', evaluate: cr01CrossingFlowCount },
|
|
334
|
+
{ id: 'RT-01', name: 'PhysicalBoundaryIntegrity', severity: 'error', evaluate: rt01PhysicalBoundaryIntegrity },
|
|
335
|
+
{ id: 'PH-01', name: 'PhysicalModCompleteness', severity: 'info', evaluate: ph01PhysicalModCompleteness },
|
|
336
|
+
{ id: 'CA-01', name: 'CapabilityAllocation', severity: 'error', evaluate: ca01CapabilityAllocation },
|
|
337
|
+
{ id: 'IO-01', name: 'CrossModuleIOCompleteness', severity: 'warning', evaluate: io01CrossModuleCompleteness },
|
|
338
|
+
];
|
|
339
|
+
export function evaluateAORules(graph) {
|
|
340
|
+
return AO_RULES.flatMap(r => r.evaluate(graph));
|
|
341
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RC conformance rules (CR-GC-253) — graph↔code resolution, the RESOLUTION
|
|
3
|
+
* twins of the presence rules R-20 (codeRef) / R-19 (testRef).
|
|
4
|
+
*
|
|
5
|
+
* Rules stay in THIS library (one rule base per onto set — no rule definitions
|
|
6
|
+
* in executor codebases). They are pure functions over (graph, facts): all
|
|
7
|
+
* filesystem/parser work happens in the executor (graphcode extracts CodeFacts
|
|
8
|
+
* from the repo), so this module stays I/O-free and browser-bundlable.
|
|
9
|
+
*
|
|
10
|
+
* CodeFacts semantics: `files` is keyed by repo-relative path and MUST contain
|
|
11
|
+
* an entry for every file referenced by a codeRef/testRef the extractor saw.
|
|
12
|
+
* A MISSING key is treated like `exists:false` — an extractor gap must surface
|
|
13
|
+
* loudly as a violation, never as a silent pass.
|
|
14
|
+
*
|
|
15
|
+
* @sigloch/contracts/se — single source of truth for SE validation rules.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod/v4';
|
|
18
|
+
import type { OntologyGraph } from './ontology.js';
|
|
19
|
+
import type { RuleSeverity, RuleViolation } from './rules.js';
|
|
20
|
+
/** Parser facts about one source file (extracted by the executor). */
|
|
21
|
+
export declare const FileFactsSchema: z.ZodObject<{
|
|
22
|
+
exists: z.ZodBoolean;
|
|
23
|
+
declaredSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
24
|
+
testCases: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
25
|
+
importedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
26
|
+
parsedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export type FileFacts = z.infer<typeof FileFactsSchema>;
|
|
29
|
+
/** One file→file import edge (repo-relative paths) — the module-drift evidence (CR-212). */
|
|
30
|
+
export declare const ImportEdgeSchema: z.ZodObject<{
|
|
31
|
+
from: z.ZodString;
|
|
32
|
+
to: z.ZodString;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type ImportEdge = z.infer<typeof ImportEdgeSchema>;
|
|
35
|
+
/**
|
|
36
|
+
* Deterministic, serializable code facts — the data contract between the
|
|
37
|
+
* facts extractor (graphcode: TS parser today, ast-grep later — CR-GC-254)
|
|
38
|
+
* and the RC rules. Testable without any filesystem.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CodeFactsSchema: z.ZodObject<{
|
|
41
|
+
files: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
42
|
+
exists: z.ZodBoolean;
|
|
43
|
+
declaredSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
44
|
+
testCases: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
45
|
+
importedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
46
|
+
parsedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
47
|
+
}, z.core.$strip>>;
|
|
48
|
+
importEdges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
49
|
+
from: z.ZodString;
|
|
50
|
+
to: z.ZodString;
|
|
51
|
+
}, z.core.$strip>>>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export type CodeFacts = z.infer<typeof CodeFactsSchema>;
|
|
54
|
+
/** A conformance rule: pure over (graph, facts) — never touches I/O itself. */
|
|
55
|
+
export interface ConformanceRuleDefinition {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
severity: RuleSeverity;
|
|
59
|
+
evaluate: (graph: OntologyGraph, facts: CodeFacts) => RuleViolation[];
|
|
60
|
+
}
|
|
61
|
+
/** All RC conformance rules — evaluated by executors that can supply CodeFacts. */
|
|
62
|
+
export declare const CODE_CONFORMANCE_RULES: ConformanceRuleDefinition[];
|
|
63
|
+
/** Run all RC rules against a graph + extracted code facts. */
|
|
64
|
+
export declare function evaluateConformanceRules(graph: OntologyGraph, facts: CodeFacts): RuleViolation[];
|