@sigloch/contracts 10.10.0 → 10.12.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 +187 -46
- package/dist/se/cr-quality-rules.d.ts +5 -0
- package/dist/se/cr-quality-rules.js +6 -1
- package/dist/se/fmea-rules.js +4 -4
- package/dist/se/function-criticality.d.ts +8 -22
- package/dist/se/function-criticality.js +71 -1
- package/dist/se/grammar-snapshot.d.ts +5 -5
- package/dist/se/grammar-snapshot.js +12 -10
- package/dist/se/index.d.ts +1 -1
- package/dist/se/index.js +1 -1
- package/dist/se/metric-rules.d.ts +20 -61
- package/dist/se/metric-rules.js +90 -1
- package/dist/se/ontology.d.ts +27 -0
- package/dist/se/ontology.js +16 -0
- package/dist/se/readiness.js +4 -5
- package/dist/se/rule-help.js +9 -13
- package/dist/se/rules.js +8 -38
- 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/rule-help.js
CHANGED
|
@@ -300,7 +300,15 @@ export const RULE_HELP = {
|
|
|
300
300
|
},
|
|
301
301
|
'RC-07': {
|
|
302
302
|
plain: "A change request is marked done in the model but still open in the folder (or the other way round), or an open change request has no entry in the model → the folder decides; fix the model.",
|
|
303
|
-
se: "`CR` node whose `status` (closed = done
|
|
303
|
+
se: "`CR` node whose `status` (closed = done; CR-SM-362) contradicts `docs/cr/open|done/`, or an OPEN CR file with no `CR` node (anchored at the first `SYS`). Nodes without a file are not reported — history. Absent `crFiles` is SILENCE (CR-SM-329).",
|
|
304
|
+
},
|
|
305
|
+
'RC-08': {
|
|
306
|
+
plain: "A data contract in the model points at a type, which vanishes at runtime → write it as a Zod schema, so whoever produces the data can prove it and whoever reads it can check it.",
|
|
307
|
+
se: "`SCHEMA` whose `realRef` symbol is declared but is not a Zod schema (extractor field `zodSymbols`). Every producer needs a checkable schema — \"nobody parses it\" only means the reader is not known yet. Absent `zodSymbols` is SILENCE. Owns the type-binding cause: RC-04 stays quiet on such a SCHEMA (CR-SM-358).",
|
|
308
|
+
},
|
|
309
|
+
'RC-09': {
|
|
310
|
+
plain: "Code somewhere reads a data contract on its own, but the model does not know that place → go through the one translator the model names, or add the new translator to the model.",
|
|
311
|
+
se: "Zod-bound `SCHEMA` parsed (import + `.parse`/`.safeParse`) in a file that is neither its definition nor the `realRef` of a `FUNC` io-connected to a `FLOW` of this SCHEMA. The first code → model check: a second translator of one contract is a parallel path (spike CR-GC-637 — such paths share a contract, they are not similar). One finding per SCHEMA; test files exempt; runs only with `fileScope: 'all'` (CR-SM-358).",
|
|
304
312
|
},
|
|
305
313
|
'RD-01': {
|
|
306
314
|
plain: "A smallest-piece feature has nothing built to fulfil it → add what implements it.",
|
|
@@ -311,10 +319,6 @@ export const RULE_HELP = {
|
|
|
311
319
|
plain: "You split a feature into smaller features but you're also building the big one directly → build only the small pieces, not both.",
|
|
312
320
|
se: "Parent `REQ` (has `compose`→`REQ` children) carrying a direct `FUNC` `satisfy`; the satisfy belongs on the children.",
|
|
313
321
|
},
|
|
314
|
-
'RD-03': {
|
|
315
|
-
plain: "You split a feature into pieces, but all the pieces are handled by the same one thing — the split may be pointless → consider merging them.",
|
|
316
|
-
se: "Parent `REQ` whose children all share one satisfier.",
|
|
317
|
-
},
|
|
318
322
|
'RD-04': {
|
|
319
323
|
plain: "One thing has more than 9 parts directly under it → group them, so each level stays readable.",
|
|
320
324
|
se: "Decomposition breadth above `metricPolicy.decompositionBreadth.warning` children on one level — `FUNC` `compose` `FUNC`, `SYS` `compose` `MOD`, a `MOD`'s sub-`MOD`s plus its own leaf `FUNC` (`allocate`), and the root `FUNC` forest anchored at `SYS` (CR-SM-282). Default 9, the upper end of 7±2 (CR-SM-296). CR-SM-311: a module level counts its own functions again — breadth, not size; module coupling is R-04. The lower bound is RD-05.",
|
|
@@ -347,14 +351,6 @@ export const RULE_HELP = {
|
|
|
347
351
|
se: "`UC` description shorter than 10 characters or containing TBD/TODO/FIXME/placeholder/XXX. Description IS the goal (CR-150).",
|
|
348
352
|
prompt: "se:author-uc",
|
|
349
353
|
},
|
|
350
|
-
'UC-05': {
|
|
351
|
-
plain: "A scenario does not say what must be true once it has finished → add that as a requirement.",
|
|
352
|
-
se: "`UC` with no `compose`d `REQ` carrying `kinds:[\"postcondition\"]`.",
|
|
353
|
-
},
|
|
354
|
-
'UC-06': {
|
|
355
|
-
plain: "A scenario does not say what must be true before it can start → add that as a requirement.",
|
|
356
|
-
se: "`UC` with no `compose`d `REQ` carrying `kinds:[\"precondition\"]`.",
|
|
357
|
-
},
|
|
358
354
|
'VR-01': {
|
|
359
355
|
plain: "A test exists but no result was ever recorded, so nobody knows if it passed → record the outcome.",
|
|
360
356
|
se: "`TEST` with no `testResult` attribute — assumed pending, never assumed green.",
|
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';
|
|
@@ -663,35 +663,6 @@ function decompositionConsistency(graph) {
|
|
|
663
663
|
context: { element_type: req.type, element_name: req.name },
|
|
664
664
|
}));
|
|
665
665
|
}
|
|
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
666
|
function breadthCounts(graph) {
|
|
696
667
|
const idx = indexOf(graph);
|
|
697
668
|
// CR-SM-264: `typeOf` und `byId` baute diese Regel je Aufruf selbst — der Index hat beide.
|
|
@@ -1162,7 +1133,7 @@ function testMustHaveRunnableBinding(graph) {
|
|
|
1162
1133
|
.filter(e => e.attributes?.concept !== true) // concept-only TESTs are exempt
|
|
1163
1134
|
// CR-SM-231: `testRefs` (Array, min 1) statt `testRef` (Objekt). Semantik unveraendert —
|
|
1164
1135
|
// Praesenz und Form, keine Datei-Existenz; die pruefst RC-02.
|
|
1165
|
-
.filter(e =>
|
|
1136
|
+
.filter(e => readTestRefs(e.attributes).state !== 'bound')
|
|
1166
1137
|
.map(test => ({
|
|
1167
1138
|
rule_id: 'R-19',
|
|
1168
1139
|
severity: 'warning',
|
|
@@ -1194,12 +1165,12 @@ function testFileExclusivity(graph) {
|
|
|
1194
1165
|
for (const el of graph.elements) {
|
|
1195
1166
|
if (el.type !== 'TEST')
|
|
1196
1167
|
continue;
|
|
1197
|
-
const parsed =
|
|
1198
|
-
if (
|
|
1168
|
+
const parsed = readTestRefs(el.attributes);
|
|
1169
|
+
if (parsed.state !== 'bound')
|
|
1199
1170
|
continue; // keine/ungueltige Bindung → R-19-Gebiet
|
|
1200
1171
|
// Innerhalb EINES Knotens ist dieselbe Datei zweimal kein Konflikt, sondern eine
|
|
1201
1172
|
// Redundanz — sie wuerde sonst als Selbstkollision gemeldet.
|
|
1202
|
-
for (const file of new Set(parsed.
|
|
1173
|
+
for (const file of new Set(parsed.value.map(r => r.file))) {
|
|
1203
1174
|
const owners = claimedBy.get(file) ?? [];
|
|
1204
1175
|
owners.push(el.id);
|
|
1205
1176
|
claimedBy.set(file, owners);
|
|
@@ -1244,7 +1215,7 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1244
1215
|
const composeFuncChildren = (id) => graph.traces
|
|
1245
1216
|
.filter(t => t.source === id && t.type === 'compose' && funcById.has(t.target))
|
|
1246
1217
|
.map(t => t.target);
|
|
1247
|
-
const hasCodeRef = (el) =>
|
|
1218
|
+
const hasCodeRef = (el) => readRealRef(el.attributes).state === 'bound';
|
|
1248
1219
|
const isExempt = (el) => el.attributes?.concept === true || el.attributes?.external === true;
|
|
1249
1220
|
// A FUNC is bound iff it is exempt, has a codeRef, or is a parent whose
|
|
1250
1221
|
// compose→FUNC children are all bound (recursive, cycle-safe).
|
|
@@ -1501,7 +1472,7 @@ function schemaMustHaveSchemaRef(graph) {
|
|
|
1501
1472
|
const idx = indexOf(graph);
|
|
1502
1473
|
return idx.elementsOfType('SCHEMA')
|
|
1503
1474
|
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
1504
|
-
.filter(e =>
|
|
1475
|
+
.filter(e => readRealRef(e.attributes).state !== 'bound')
|
|
1505
1476
|
.map(sc => ({
|
|
1506
1477
|
rule_id: 'R-26',
|
|
1507
1478
|
severity: 'warning',
|
|
@@ -1526,7 +1497,7 @@ function schemaMustHaveContractTest(graph) {
|
|
|
1526
1497
|
const tests = idx.elementsOfType('TEST');
|
|
1527
1498
|
return idx.elementsOfType('SCHEMA')
|
|
1528
1499
|
.filter(sc => sc.attributes?.concept !== true)
|
|
1529
|
-
.filter(sc => { const r =
|
|
1500
|
+
.filter(sc => { const r = readRealRef(sc.attributes); return r.state === 'bound' && r.value.symbol !== undefined; })
|
|
1530
1501
|
.filter(sc => !graph.traces.some(t => t.type === 'verify' && t.target === sc.id && typeOf.get(t.source) === 'TEST'))
|
|
1531
1502
|
.map(sc => ({
|
|
1532
1503
|
rule_id: 'R-32',
|
|
@@ -1686,7 +1657,6 @@ export const V3_RULES = [
|
|
|
1686
1657
|
{ id: 'R-32', name: 'SCHEMA must have contract TEST', severity: 'warning', evaluate: schemaMustHaveContractTest, domain: ['SCHEMA'] },
|
|
1687
1658
|
{ id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', evaluate: unresolvedRequirement, domain: ['REQ'] },
|
|
1688
1659
|
{ 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
1660
|
{ id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', evaluate: decompositionBreadth, domain: ['FUNC', 'MOD', 'SYS'] },
|
|
1691
1661
|
{ id: 'RD-05', name: 'Decomposition too narrow', severity: 'warning', evaluate: decompositionNarrow, domain: ['FUNC', 'MOD', 'SYS'] },
|
|
1692
1662
|
{ 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
|
}
|