@sigloch/contracts 10.10.0 → 10.13.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/conformance-rules.d.ts +6 -1
- package/dist/se/conformance-rules.js +227 -49
- package/dist/se/cr-quality-rules.d.ts +5 -0
- package/dist/se/cr-quality-rules.js +78 -1
- package/dist/se/evaluate-all.d.ts +8 -1
- package/dist/se/evaluate-all.js +32 -29
- package/dist/se/fchain-quality-rules.d.ts +2 -1
- package/dist/se/fchain-quality-rules.js +132 -0
- package/dist/se/fmea-rules.js +22 -12
- package/dist/se/function-criticality.d.ts +8 -22
- package/dist/se/function-criticality.js +71 -1
- package/dist/se/grammar-snapshot.d.ts +9 -9
- package/dist/se/grammar-snapshot.js +34 -18
- package/dist/se/index.d.ts +3 -3
- package/dist/se/index.js +3 -3
- package/dist/se/meta-model.js +27 -16
- package/dist/se/metric-rules.d.ts +20 -61
- package/dist/se/metric-rules.js +90 -1
- package/dist/se/ontology.d.ts +69 -21
- package/dist/se/ontology.js +68 -16
- package/dist/se/readiness.d.ts +14 -0
- package/dist/se/readiness.js +33 -7
- package/dist/se/rule-help.js +28 -20
- package/dist/se/rules.d.ts +16 -0
- package/dist/se/rules.js +63 -64
- package/dist/se/uc-quality-rules.d.ts +0 -2
- package/dist/se/uc-quality-rules.js +2 -40
- package/dist/se/view-rules.js +5 -5
- package/package.json +1 -1
package/dist/se/rules.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @sigloch/contracts/se — single source of truth for SE validation rules.
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod/v4';
|
|
7
|
-
import { ElementType, TraceType,
|
|
7
|
+
import { ElementType, TraceType, readTestRefs, readRealRef } from './ontology.js';
|
|
8
8
|
import { traceRejection, admittingAlternatives, isValidTrace, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs } from './meta-model.js';
|
|
9
9
|
import { indexOf } from './graph-index.js';
|
|
10
10
|
import { moduleCrossings, subtreeFuncs } from './module-crossings.js';
|
|
@@ -35,6 +35,17 @@ export const ViolationContext = z.object({
|
|
|
35
35
|
* unterschlagen.
|
|
36
36
|
*/
|
|
37
37
|
also_affects: z.array(z.string()).optional(),
|
|
38
|
+
/**
|
|
39
|
+
* CR-SM-363 (FC-05): die NEBENKOMPONENTEN einer zerfallenen FCHAIN, je mit Ursache —
|
|
40
|
+
* `missing-link` (Haupt- und Nebenteil sind ueber eine FUNC ausserhalb der Kette verbunden;
|
|
41
|
+
* `via` nennt sie) oder `bag` (nur eine gemeinsame Quelle, parallele Dienste). EIN Befund je
|
|
42
|
+
* Kette (CR-SM-242), die Komponenten hier statt als n Befunde.
|
|
43
|
+
*/
|
|
44
|
+
side_components: z.array(z.object({
|
|
45
|
+
members: z.array(z.string()),
|
|
46
|
+
cause: z.enum(['missing-link', 'bag']),
|
|
47
|
+
via: z.array(z.string()),
|
|
48
|
+
})).optional(),
|
|
38
49
|
/**
|
|
39
50
|
* CR-SM-341: der Ablehnungsgrund von R-18, maschinenlesbar statt nur als Satz.
|
|
40
51
|
*
|
|
@@ -242,24 +253,15 @@ function funcMustSatisfyReq(graph) {
|
|
|
242
253
|
.map(st => idx.byId.get(st.target))
|
|
243
254
|
.filter((e) => !!e)
|
|
244
255
|
: [];
|
|
245
|
-
// Also include constraints whose description mentions this func's name
|
|
246
|
-
const namePattern = fn.name.toLowerCase();
|
|
247
|
-
const matchingConstraints = allReqs.filter(r => r.kinds?.includes('non-functional') && r.description.toLowerCase().includes(namePattern));
|
|
248
256
|
// CR-SM-346 (ITEM-2026-299): der Kandidatenkreis wird am META-MODELL gefiltert, nicht
|
|
249
|
-
// an einer zweiten, hier abgeschriebenen kinds-Liste
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// gab korrekt `null` zurueck. 86 Befunde, 0 Empfehlungen: der Befund war unbedienbar.
|
|
257
|
+
// an einer zweiten, hier abgeschriebenen kinds-Liste — `isValidTrace` statt Eigenbau: eine
|
|
258
|
+
// Regel, die Kandidaten anbietet, die ihr eigenes Meta-Modell abweist, erzeugt Waste statt
|
|
259
|
+
// Fuehrung. `FUNC -satisfy-> REQ` laesst nur `functional` zu (CR-SM-366).
|
|
253
260
|
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
// `isValidTrace` statt Eigenbau: eine Regel, die Kandidaten anbietet, die ihr eigenes
|
|
260
|
-
// Meta-Modell abweist, erzeugt Waste statt Fuehrung — und zwei Wahrheiten ueber dieselbe
|
|
261
|
-
// Grammatik sind genau der Defekt, den CR-SM-286/-302 beseitigt haben.
|
|
262
|
-
const candidates = [...new Map([...siblingReqs, ...matchingConstraints, ...allReqs].map(e => [e.id, e])).values()]
|
|
261
|
+
// CR-SM-366: der fruehere `matchingConstraints`-Zweig (non-functional-REQ, deren Text den
|
|
262
|
+
// FUNC-Namen nennt) ist entfallen. Er zog genau die Sorte herein, die das Muster abweist;
|
|
263
|
+
// seit ein REQ genau EIN kind traegt, konnte kein Treffer den Filter je passieren.
|
|
264
|
+
const candidates = [...new Map([...siblingReqs, ...allReqs].map(e => [e.id, e])).values()]
|
|
263
265
|
.filter(r => isValidTrace({ source: 'FUNC', target: 'REQ', type: 'satisfy', targetKinds: r.kinds }));
|
|
264
266
|
return {
|
|
265
267
|
rule_id: 'R-02',
|
|
@@ -663,35 +665,6 @@ function decompositionConsistency(graph) {
|
|
|
663
665
|
context: { element_type: req.type, element_name: req.name },
|
|
664
666
|
}));
|
|
665
667
|
}
|
|
666
|
-
// ---------------------------------------------------------------------------
|
|
667
|
-
// RD-03: No premature decomposition (all children same satisfy target)
|
|
668
|
-
// ---------------------------------------------------------------------------
|
|
669
|
-
function noPrematureDecomposition(graph) {
|
|
670
|
-
const idx = indexOf(graph);
|
|
671
|
-
const reqs = idx.elementsOfType('REQ');
|
|
672
|
-
const parentReqs = reqs.filter(req => idx.out(req.id, 'compose').some(t => idx.typeOf(t.target) === 'REQ'));
|
|
673
|
-
return parentReqs
|
|
674
|
-
.filter(parent => {
|
|
675
|
-
const childIds = graph.traces
|
|
676
|
-
.filter(t => t.source === parent.id && t.type === 'compose' &&
|
|
677
|
-
idx.typeOf(t.target) === 'REQ')
|
|
678
|
-
.map(t => t.target);
|
|
679
|
-
if (childIds.length < 2)
|
|
680
|
-
return false;
|
|
681
|
-
const satisfyTargets = childIds.map(cid => idx.in(cid, 'satisfy').filter(t => t.source !== cid).map(t => t.source));
|
|
682
|
-
// All children satisfied by the same single source
|
|
683
|
-
const allSame = satisfyTargets.every(targets => targets.length === 1 && targets[0] === satisfyTargets[0]?.[0]);
|
|
684
|
-
return allSame && satisfyTargets[0]?.length === 1;
|
|
685
|
-
})
|
|
686
|
-
.map(req => ({
|
|
687
|
-
rule_id: 'RD-03',
|
|
688
|
-
severity: 'info',
|
|
689
|
-
element_id: req.id,
|
|
690
|
-
message: `${req.id} children all have the same satisfy target — decomposition may be unnecessary`,
|
|
691
|
-
fix_hint: 'Consider merging sub-REQs back into parent if they share the same verifiable level',
|
|
692
|
-
context: { element_type: req.type, element_name: req.name },
|
|
693
|
-
}));
|
|
694
|
-
}
|
|
695
668
|
function breadthCounts(graph) {
|
|
696
669
|
const idx = indexOf(graph);
|
|
697
670
|
// CR-SM-264: `typeOf` und `byId` baute diese Regel je Aufruf selbst — der Index hat beide.
|
|
@@ -856,7 +829,9 @@ function actorMustHaveTrace(graph) {
|
|
|
856
829
|
severity: 'warning',
|
|
857
830
|
element_id: a.id,
|
|
858
831
|
message: `${a.id} has no io traces (disconnected actor)`,
|
|
859
|
-
|
|
832
|
+
// CR-SM-371: ein ACTOR hat genau zwei legale Kanten (ACTOR -io-> FLOW, FLOW -io-> ACTOR);
|
|
833
|
+
// der UC wird ueber den FLOW an einer FUNC seiner Kette erreicht, nie direkt (R-18).
|
|
834
|
+
fix_hint: `Wire the actor through a FLOW via graph_mutate Format-E: \`${a.id} -io-> FLOW-x\` (it sends) or \`FLOW-x -io-> ${a.id}\` (it receives), plus \`FLOW-x -io-> FUNC-y\` or \`FUNC-y -io-> FLOW-x\` for a FUNC in the use case's chain — never an io edge to the UC itself`,
|
|
860
835
|
context: {
|
|
861
836
|
element_type: a.type,
|
|
862
837
|
element_name: a.name,
|
|
@@ -956,7 +931,6 @@ function validTracePattern(graph) {
|
|
|
956
931
|
});
|
|
957
932
|
if (!why)
|
|
958
933
|
return [];
|
|
959
|
-
const { detail, hint } = explainRejection(why, t, src, tgt);
|
|
960
934
|
// CR-SM-341: derselbe Grund, den `detail` in Prosa giesst — hier zusaetzlich als Struktur.
|
|
961
935
|
// Keine zweite Rechnung: `why` liegt vor, die Alternativen befragen `isValidTrace`.
|
|
962
936
|
const shape = {
|
|
@@ -965,6 +939,7 @@ function validTracePattern(graph) {
|
|
|
965
939
|
targetKinds: idx.byId.get(t.target)?.kinds,
|
|
966
940
|
};
|
|
967
941
|
const alt = admittingAlternatives(shape);
|
|
942
|
+
const { detail, hint } = explainRejection(why, t, src, tgt, alt.sources);
|
|
968
943
|
const where = why.reason === 'no-pattern' ? undefined : why.pattern.where;
|
|
969
944
|
return [{
|
|
970
945
|
rule_id: 'R-18',
|
|
@@ -999,7 +974,7 @@ function validTracePattern(graph) {
|
|
|
999
974
|
* `kinds` steht da und passt nicht (Kante heben oder Kind korrigieren). Wer das nicht
|
|
1000
975
|
* unterscheiden kann, sucht am falschen Ende.
|
|
1001
976
|
*/
|
|
1002
|
-
function explainRejection(why, t, src, tgt) {
|
|
977
|
+
function explainRejection(why, t, src, tgt, admittingSources) {
|
|
1003
978
|
if (why.reason === 'no-pattern') {
|
|
1004
979
|
return {
|
|
1005
980
|
detail: `${src} → ${tgt} is not a valid ${t.type} pattern`,
|
|
@@ -1020,7 +995,11 @@ function explainRejection(why, t, src, tgt) {
|
|
|
1020
995
|
}
|
|
1021
996
|
: {
|
|
1022
997
|
detail: `${admits}, but ${end} declares {${[...why.declared].sort().join(', ')}}`,
|
|
1023
|
-
|
|
998
|
+
// CR-SM-366: den Erfueller NENNEN statt "FCHAIN carries every kind" — das galt nie fuer
|
|
999
|
+
// die Code-Bindung und gilt seit dem FCHAIN-where gar nicht mehr.
|
|
1000
|
+
hint: admittingSources.length > 0
|
|
1001
|
+
? `Correct '${where.field}' on ${end}, or ${t.type} it from ${admittingSources.join(' | ')} — the pattern admitting {${[...why.declared].sort().join(', ')}}`
|
|
1002
|
+
: `Correct '${where.field}' on ${end} to a subset of {${allowed}} — no source type admits {${[...why.declared].sort().join(', ')}} (retired kind? split a mixed REQ)`,
|
|
1024
1003
|
};
|
|
1025
1004
|
}
|
|
1026
1005
|
/**
|
|
@@ -1162,7 +1141,7 @@ function testMustHaveRunnableBinding(graph) {
|
|
|
1162
1141
|
.filter(e => e.attributes?.concept !== true) // concept-only TESTs are exempt
|
|
1163
1142
|
// CR-SM-231: `testRefs` (Array, min 1) statt `testRef` (Objekt). Semantik unveraendert —
|
|
1164
1143
|
// Praesenz und Form, keine Datei-Existenz; die pruefst RC-02.
|
|
1165
|
-
.filter(e =>
|
|
1144
|
+
.filter(e => readTestRefs(e.attributes).state !== 'bound')
|
|
1166
1145
|
.map(test => ({
|
|
1167
1146
|
rule_id: 'R-19',
|
|
1168
1147
|
severity: 'warning',
|
|
@@ -1194,12 +1173,12 @@ function testFileExclusivity(graph) {
|
|
|
1194
1173
|
for (const el of graph.elements) {
|
|
1195
1174
|
if (el.type !== 'TEST')
|
|
1196
1175
|
continue;
|
|
1197
|
-
const parsed =
|
|
1198
|
-
if (
|
|
1176
|
+
const parsed = readTestRefs(el.attributes);
|
|
1177
|
+
if (parsed.state !== 'bound')
|
|
1199
1178
|
continue; // keine/ungueltige Bindung → R-19-Gebiet
|
|
1200
1179
|
// Innerhalb EINES Knotens ist dieselbe Datei zweimal kein Konflikt, sondern eine
|
|
1201
1180
|
// Redundanz — sie wuerde sonst als Selbstkollision gemeldet.
|
|
1202
|
-
for (const file of new Set(parsed.
|
|
1181
|
+
for (const file of new Set(parsed.value.map(r => r.file))) {
|
|
1203
1182
|
const owners = claimedBy.get(file) ?? [];
|
|
1204
1183
|
owners.push(el.id);
|
|
1205
1184
|
claimedBy.set(file, owners);
|
|
@@ -1244,7 +1223,7 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1244
1223
|
const composeFuncChildren = (id) => graph.traces
|
|
1245
1224
|
.filter(t => t.source === id && t.type === 'compose' && funcById.has(t.target))
|
|
1246
1225
|
.map(t => t.target);
|
|
1247
|
-
const hasCodeRef = (el) =>
|
|
1226
|
+
const hasCodeRef = (el) => readRealRef(el.attributes).state === 'bound';
|
|
1248
1227
|
const isExempt = (el) => el.attributes?.concept === true || el.attributes?.external === true;
|
|
1249
1228
|
// A FUNC is bound iff it is exempt, has a codeRef, or is a parent whose
|
|
1250
1229
|
// compose→FUNC children are all bound (recursive, cycle-safe).
|
|
@@ -1299,7 +1278,7 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1299
1278
|
severity: 'warning',
|
|
1300
1279
|
element_id: fn.id,
|
|
1301
1280
|
message: `${fn.id} is not fully realized — unbound compose ${leafList.length === 1 ? 'child' : 'children'}: ${leafList.join(', ')}`,
|
|
1302
|
-
fix_hint: `
|
|
1281
|
+
fix_hint: `Bind the leaves via graph_mutate Format-E (${leafList.map((id) => `\`~ ${id} @realRef {"file":…,"symbol":…}\``).join(', ')}) — the parent inherits their binding`,
|
|
1303
1282
|
context: { element_type: fn.type, element_name: fn.name },
|
|
1304
1283
|
});
|
|
1305
1284
|
}
|
|
@@ -1329,6 +1308,10 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1329
1308
|
// Co-adjacency at a shared FLOW is not an asserted interface — treating it
|
|
1330
1309
|
// as one produced P·C findings per hub FLOW and made reuse the expensive
|
|
1331
1310
|
// choice. The FCHAIN is the modelled claim; the test is owed on the claim.
|
|
1311
|
+
// CR-SM-376: second way to cover a chain. Since CR-SM-366 an FCHAIN carries
|
|
1312
|
+
// only NFRs; its effect is covered through its members when EVERY member
|
|
1313
|
+
// FUNC (FCHAIN ─compose→ FUNC) satisfies at least one REQ. Verifying those
|
|
1314
|
+
// member REQs is R-01's job, not R-21's.
|
|
1332
1315
|
// ---------------------------------------------------------------------------
|
|
1333
1316
|
function fchainMustHaveIntegrationTest(graph, policy) {
|
|
1334
1317
|
const idx = indexOf(graph);
|
|
@@ -1362,6 +1345,22 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1362
1345
|
testedChains.add(t.source);
|
|
1363
1346
|
}
|
|
1364
1347
|
}
|
|
1348
|
+
// CR-SM-376: der zweite Weg — die Kette ist ueber ihre Glieder gedeckt, wenn JEDE Glied-FUNC
|
|
1349
|
+
// (direkte FCHAIN -compose-> FUNC, dieselbe Definition wie `chainsByFunc`) eine REQ erfuellt.
|
|
1350
|
+
// `unmetMembers` liefert die Glieder ohne REQ; leer = gedeckt. Eine Kette ohne Glied kann hier
|
|
1351
|
+
// nicht auftauchen: ZWEIG 2 hat mindestens die beiden Enden der Uebergabe als Glieder.
|
|
1352
|
+
const funcsWithReq = new Set(idx.tracesOfType('satisfy').filter(t => isFunc(t.source) && typeOf.get(t.target) === 'REQ').map(t => t.source));
|
|
1353
|
+
const membersOf = new Map();
|
|
1354
|
+
for (const [fn, chains] of chainsOf) {
|
|
1355
|
+
for (const ch of chains) {
|
|
1356
|
+
const list = membersOf.get(ch);
|
|
1357
|
+
if (list)
|
|
1358
|
+
list.push(fn);
|
|
1359
|
+
else
|
|
1360
|
+
membersOf.set(ch, [fn]);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const unmetMembers = (ch) => (membersOf.get(ch) ?? []).filter(fn => !funcsWithReq.has(fn)).sort();
|
|
1365
1364
|
// CR-SM-313: die Infrastruktur-Ausnahme. Die ZAHL kommt aus der Kennzahl (CR-SM-314), nicht
|
|
1366
1365
|
// aus einer zweiten Rechnung — eine Uebergabe an eine Funktion, die quer durch alle Ketten
|
|
1367
1366
|
// laeuft, ist kein fehlender Integrationstest, sondern ein Bus. `null` = keine Ausnahme.
|
|
@@ -1401,8 +1400,9 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1401
1400
|
});
|
|
1402
1401
|
continue;
|
|
1403
1402
|
}
|
|
1404
|
-
// ZWEIG 2 — gemeinsame Kette, aber keine davon
|
|
1405
|
-
|
|
1403
|
+
// ZWEIG 2 — gemeinsame Kette, aber keine davon gedeckt: Befund an der Kette. Gedeckt heisst
|
|
1404
|
+
// (a) verifizierte Ketten-REQ oder (b, CR-SM-376) jede Glied-FUNC erfuellt eine REQ.
|
|
1405
|
+
if (shared.some(ch => testedChains.has(ch) || unmetMembers(ch).length === 0))
|
|
1406
1406
|
continue;
|
|
1407
1407
|
const anchor = shared[0];
|
|
1408
1408
|
const key = `${anchor}|${p}>${c}`;
|
|
@@ -1414,8 +1414,8 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1414
1414
|
rule_id: 'R-21',
|
|
1415
1415
|
severity: 'warning',
|
|
1416
1416
|
element_id: anchor,
|
|
1417
|
-
message: `${anchor} has no integration test covering connection ${p} → ${c}`,
|
|
1418
|
-
fix_hint: '
|
|
1417
|
+
message: `${anchor} has no integration test covering connection ${p} → ${c}; member FUNCs without a REQ: ${unmetMembers(anchor).join(', ')}`,
|
|
1418
|
+
fix_hint: 'Give each member FUNC of the chain a REQ it satisfies (FUNC -satisfy-> REQ), or attach an end-to-end non-functional REQ to the chain (FCHAIN -satisfy-> REQ) and verify it with an integration TEST',
|
|
1419
1419
|
context: { element_type: anchorEl?.type, element_name: anchorEl?.name },
|
|
1420
1420
|
});
|
|
1421
1421
|
}
|
|
@@ -1501,7 +1501,7 @@ function schemaMustHaveSchemaRef(graph) {
|
|
|
1501
1501
|
const idx = indexOf(graph);
|
|
1502
1502
|
return idx.elementsOfType('SCHEMA')
|
|
1503
1503
|
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
1504
|
-
.filter(e =>
|
|
1504
|
+
.filter(e => readRealRef(e.attributes).state !== 'bound')
|
|
1505
1505
|
.map(sc => ({
|
|
1506
1506
|
rule_id: 'R-26',
|
|
1507
1507
|
severity: 'warning',
|
|
@@ -1526,7 +1526,7 @@ function schemaMustHaveContractTest(graph) {
|
|
|
1526
1526
|
const tests = idx.elementsOfType('TEST');
|
|
1527
1527
|
return idx.elementsOfType('SCHEMA')
|
|
1528
1528
|
.filter(sc => sc.attributes?.concept !== true)
|
|
1529
|
-
.filter(sc => { const r =
|
|
1529
|
+
.filter(sc => { const r = readRealRef(sc.attributes); return r.state === 'bound' && r.value.symbol !== undefined; })
|
|
1530
1530
|
.filter(sc => !graph.traces.some(t => t.type === 'verify' && t.target === sc.id && typeOf.get(t.source) === 'TEST'))
|
|
1531
1531
|
.map(sc => ({
|
|
1532
1532
|
rule_id: 'R-32',
|
|
@@ -1679,14 +1679,13 @@ export const V3_RULES = [
|
|
|
1679
1679
|
{ id: 'R-19', name: 'Runnable TEST binding', severity: 'warning', evaluate: testMustHaveRunnableBinding, domain: ['TEST'] },
|
|
1680
1680
|
{ id: 'R-29', name: 'Test file exclusivity', severity: 'error', evaluate: testFileExclusivity, domain: ['TEST'] },
|
|
1681
1681
|
{ id: 'R-20', name: 'FUNC realRef binding', severity: 'warning', evaluate: funcMustHaveCodeBinding, domain: ['FUNC'] },
|
|
1682
|
-
{ id: 'R-21', name: 'FUNC↔FUNC handover needs a shared chain
|
|
1682
|
+
{ id: 'R-21', name: 'FUNC↔FUNC handover needs a shared chain covered by member REQs or an integration test', severity: 'warning', evaluate: fchainMustHaveIntegrationTest, domain: ['FCHAIN', 'FUNC'] },
|
|
1683
1683
|
{ id: 'R-22', name: 'FUNC must be allocated to MOD', severity: 'warning', evaluate: funcMustBeAllocated, domain: ['FUNC'] },
|
|
1684
1684
|
{ id: 'R-23', name: 'MOD must have allocated FUNC', severity: 'warning', evaluate: modMustHaveAllocatedFunc, domain: ['MOD'] },
|
|
1685
1685
|
{ id: 'R-26', name: 'SCHEMA must have realRef', severity: 'warning', evaluate: schemaMustHaveSchemaRef, domain: ['SCHEMA'] },
|
|
1686
1686
|
{ id: 'R-32', name: 'SCHEMA must have contract TEST', severity: 'warning', evaluate: schemaMustHaveContractTest, domain: ['SCHEMA'] },
|
|
1687
1687
|
{ id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', evaluate: unresolvedRequirement, domain: ['REQ'] },
|
|
1688
1688
|
{ id: 'RD-02', name: 'Decomposition consistency', severity: 'warning', evaluate: decompositionConsistency, domain: ['REQ'] },
|
|
1689
|
-
{ id: 'RD-03', name: 'No premature decomposition', severity: 'info', evaluate: noPrematureDecomposition, domain: ['REQ'] },
|
|
1690
1689
|
{ id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', evaluate: decompositionBreadth, domain: ['FUNC', 'MOD', 'SYS'] },
|
|
1691
1690
|
{ id: 'RD-05', name: 'Decomposition too narrow', severity: 'warning', evaluate: decompositionNarrow, domain: ['FUNC', 'MOD', 'SYS'] },
|
|
1692
1691
|
{ id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', evaluate: msEmptyScope, domain: ['MS'] },
|
|
@@ -8,7 +8,5 @@ export declare function uc01HasRequirements(graph: OntologyGraph): RuleViolation
|
|
|
8
8
|
export declare function uc02HasActor(graph: OntologyGraph): RuleViolation[];
|
|
9
9
|
export declare function uc03HasScenario(graph: OntologyGraph): RuleViolation[];
|
|
10
10
|
export declare function uc04GoalDefined(graph: OntologyGraph): RuleViolation[];
|
|
11
|
-
export declare function uc05HasPostcondition(graph: OntologyGraph): RuleViolation[];
|
|
12
|
-
export declare function uc06HasPrecondition(graph: OntologyGraph): RuleViolation[];
|
|
13
11
|
export declare const UC_RULES: RuleDefinition[];
|
|
14
12
|
export declare function evaluateUCRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
|
|
@@ -112,53 +112,15 @@ export function uc04GoalDefined(graph) {
|
|
|
112
112
|
}));
|
|
113
113
|
}
|
|
114
114
|
// ---------------------------------------------------------------------------
|
|
115
|
-
// UC-05: UC should have postcondition REQ (CR-165: compose only, satisfy UC→REQ invalid)
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
export function uc05HasPostcondition(graph) {
|
|
118
|
-
const idx = indexOf(graph);
|
|
119
|
-
return idx.elementsOfType('UC')
|
|
120
|
-
.filter(uc => !idx.out(uc.id, 'compose').some(t => {
|
|
121
|
-
const target = idx.byId.get(t.target);
|
|
122
|
-
return target?.type === 'REQ' && target.kinds?.includes('postcondition');
|
|
123
|
-
}))
|
|
124
|
-
.map(uc => ({
|
|
125
|
-
rule_id: 'UC-05',
|
|
126
|
-
severity: 'info',
|
|
127
|
-
element_id: uc.id,
|
|
128
|
-
message: `${uc.id} has no postcondition requirement`,
|
|
129
|
-
fix_hint: 'Add a REQ with kinds=["postcondition"] via compose trace',
|
|
130
|
-
context: { element_type: uc.type, element_name: uc.name },
|
|
131
|
-
}));
|
|
132
|
-
}
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// UC-06: UC should have precondition REQ (CR-165: compose only, satisfy UC→REQ invalid)
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
export function uc06HasPrecondition(graph) {
|
|
137
|
-
const idx = indexOf(graph);
|
|
138
|
-
return idx.elementsOfType('UC')
|
|
139
|
-
.filter(uc => !idx.out(uc.id, 'compose').some(t => {
|
|
140
|
-
const target = idx.byId.get(t.target);
|
|
141
|
-
return target?.type === 'REQ' && target.kinds?.includes('precondition');
|
|
142
|
-
}))
|
|
143
|
-
.map(uc => ({
|
|
144
|
-
rule_id: 'UC-06',
|
|
145
|
-
severity: 'info',
|
|
146
|
-
element_id: uc.id,
|
|
147
|
-
message: `${uc.id} has no precondition requirement`,
|
|
148
|
-
fix_hint: 'Add a REQ with kinds=["precondition"] via compose trace',
|
|
149
|
-
context: { element_type: uc.type, element_name: uc.name },
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
115
|
// Aggregated
|
|
154
116
|
// ---------------------------------------------------------------------------
|
|
117
|
+
// CR-SM-357: UC-05/UC-06 (Pre-/Postcondition-REQ) sind gestrichen — Schreibregel in se:author-uc,
|
|
118
|
+
// wie BQ in author-req (CR-GC-602): sie feuerten an fast jedem UC, und niemand handelte darauf.
|
|
155
119
|
export const UC_RULES = [
|
|
156
120
|
{ id: 'UC-01', name: 'UC has requirements', severity: 'warning', evaluate: uc01HasRequirements, domain: ['UC'] },
|
|
157
121
|
{ id: 'UC-02', name: 'UC has actor', severity: 'warning', evaluate: uc02HasActor, domain: ['UC'] },
|
|
158
122
|
{ id: 'UC-03', name: 'UC has scenario', severity: 'warning', evaluate: uc03HasScenario, domain: ['UC'] },
|
|
159
123
|
{ id: 'UC-04', name: 'UC has goal', severity: 'warning', evaluate: uc04GoalDefined, domain: ['UC'] },
|
|
160
|
-
{ id: 'UC-05', name: 'UC has postcondition', severity: 'info', evaluate: uc05HasPostcondition, domain: ['UC'] },
|
|
161
|
-
{ id: 'UC-06', name: 'UC has precondition', severity: 'info', evaluate: uc06HasPrecondition, domain: ['UC'] },
|
|
162
124
|
];
|
|
163
125
|
// CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
|
|
164
126
|
// ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
|
package/dist/se/view-rules.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* VR-01: TEST with a testRefs entry that has no result (info).
|
|
4
4
|
* CL-01: ACTOR without UCs in at least 2 distinct operatingModes (warning).
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { readTestRefs } from './ontology.js';
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// VR-01: ein testRefs-Eintrag ohne Ergebnis
|
|
9
9
|
//
|
|
@@ -20,17 +20,17 @@ export function vr01TestNoResult(graph) {
|
|
|
20
20
|
for (const el of graph.elements) {
|
|
21
21
|
if (el.type !== 'TEST')
|
|
22
22
|
continue;
|
|
23
|
-
const parsed =
|
|
24
|
-
if (
|
|
23
|
+
const parsed = readTestRefs(el.attributes);
|
|
24
|
+
if (parsed.state !== 'bound')
|
|
25
25
|
continue; // keine/ungueltige Bindung → R-19-Gebiet
|
|
26
|
-
const pending = parsed.
|
|
26
|
+
const pending = parsed.value.filter(ref => ref.result === undefined);
|
|
27
27
|
if (pending.length === 0)
|
|
28
28
|
continue;
|
|
29
29
|
violations.push({
|
|
30
30
|
rule_id: 'VR-01',
|
|
31
31
|
severity: 'info',
|
|
32
32
|
element_id: el.id,
|
|
33
|
-
message: `${el.id} has ${pending.length} of ${parsed.
|
|
33
|
+
message: `${el.id} has ${pending.length} of ${parsed.value.length} testRefs entries without a result — assumed pending: ${pending.map(r => r.file).join(', ')}`,
|
|
34
34
|
fix_hint: 'Record the run outcome on the entry (result, ranAt, evidence) — a binding without a result is not evidence',
|
|
35
35
|
});
|
|
36
36
|
}
|