@sigloch/contracts 6.3.0 → 9.1.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/rules.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { z } from 'zod/v4';
7
7
  import { ElementType, TraceType, TestRefsSchema, RealRefSchema } from './ontology.js';
8
- import { isValidTrace } from './meta-model.js';
8
+ import { isValidTrace, BOUNDED_PATTERNS, maxOccurs } from './meta-model.js';
9
9
  import { indexOf } from './graph-index.js';
10
10
  export const RuleSeverity = z.enum(['error', 'warning', 'info']);
11
11
  /** Candidate target for resolving a violation (e.g. a REQ to satisfy, a TEST to link). */
@@ -333,8 +333,9 @@ function testMustVerifyReq(graph) {
333
333
  // ---------------------------------------------------------------------------
334
334
  function traceConsistency(graph) {
335
335
  const idx = indexOf(graph);
336
+ // CR-SM-266 D5: kein `category !== 'audit'`-Filter mehr — das Attribut ist weg, und mit ihm
337
+ // die Moeglichkeit, eine Kante per Selbstauskunft aus der Referenzintegritaet zu nehmen.
336
338
  return graph.traces
337
- .filter(t => t.category !== 'audit')
338
339
  .filter(t => {
339
340
  const sourceExists = idx.byId.has(t.source);
340
341
  const targetExists = idx.byId.has(t.target);
@@ -361,7 +362,9 @@ function flowCompleteness(graph) {
361
362
  const idx = indexOf(graph);
362
363
  const flows = idx.elementsOfType('FLOW');
363
364
  // Producers/consumers of a FLOW are FUNCs or ACTORs (io endpoints on the
364
- // upstream/downstream side); UC may consume too. Offer both as candidates.
365
+ // upstream/downstream side). CR-SM-266 D4: UC ist KEIN Konsument mehr das Pattern
366
+ // `FLOW -io-> UC` ist entfallen, ein FLOW erreicht einen UC nur noch ueber ein Kettenglied.
367
+ // Der fix_hint hat den UC vorher noch angeboten und haette in eine R-18-Ablehnung gefuehrt.
365
368
  const sources = graph.elements.filter(e => e.type === 'FUNC' || e.type === 'ACTOR'); // Mehrtyp: ein Durchlauf ist hier billiger als vier Index-Listen zu mischen (Reihenfolge!)
366
369
  const io = idx.tracesOfType('io');
367
370
  const violations = [];
@@ -383,7 +386,7 @@ function flowCompleteness(graph) {
383
386
  severity: 'warning',
384
387
  element_id: f.id,
385
388
  message: `${f.id} has no consumer (outgoing io)`,
386
- fix_hint: 'Link a FUNC, ACTOR, or UC as the target via io trace',
389
+ fix_hint: 'Link a FUNC or ACTOR as the target via io trace',
387
390
  context: ctx,
388
391
  });
389
392
  }
@@ -721,23 +724,72 @@ function validTracePattern(graph) {
721
724
  const idx = indexOf(graph);
722
725
  // CR-SM-264: die Typ-Map kommt aus dem Index statt je Aufruf neu gebaut zu werden.
723
726
  const typeById = { get: (id) => idx.typeOf(id) };
727
+ // CR-SM-266 D5: der `category !== 'audit'`-Filter ist WEG. Er war die Ausnahme fuer das
728
+ // einzige audit-Pattern (`SESSION -produces-> *`); mit dessen Entfernung waere er zum
729
+ // Schlupfloch geworden — eine Kante mit `category: 'audit'` haette die Matrix komplett
730
+ // umgangen. Das Attribut existiert nicht mehr (ontology.ts), hier faellt der Leser mit.
724
731
  return graph.traces
725
- .filter(t => t.category !== 'audit')
726
732
  .filter(t => {
727
733
  const src = typeById.get(t.source);
728
734
  const tgt = typeById.get(t.target);
729
735
  if (!src || !tgt)
730
736
  return false; // dangling endpoint → R-08, not R-18
731
- return !isValidTrace({ source: src, target: tgt, type: t.type, label: t.label });
737
+ // CR-SM-266 B: die where-Patterns lesen die Kinds des betroffenen Endes mit.
738
+ return !isValidTrace({
739
+ source: src, target: tgt, type: t.type, label: t.label,
740
+ sourceKinds: idx.byId.get(t.source)?.kinds,
741
+ targetKinds: idx.byId.get(t.target)?.kinds,
742
+ });
732
743
  })
733
- .map(t => ({
744
+ .map((t) => ({
734
745
  rule_id: 'R-18',
735
746
  severity: 'error',
736
747
  element_id: t.source,
737
748
  message: `Invalid trace ${t.source} -${t.type}-> ${t.target}: ` +
738
749
  `${typeById.get(t.source)} → ${typeById.get(t.target)} is not a valid ${t.type} pattern`,
739
750
  fix_hint: 'Use a trace type whose TRACE_PATTERNS allows this source/target element-type pair',
740
- }));
751
+ }))
752
+ .concat(cardinalityViolations(graph));
753
+ }
754
+ /**
755
+ * CR-SM-266b — das ZWEITE BEIN von R-18: die Kardinalitaets-Obergrenzen.
756
+ *
757
+ * Bewusst dieselbe `rule_id` und keine eigene, nach dem Muster von R-10 (Produzent/Konsument)
758
+ * und R-15 seit CR-SM-249: die Aussage ist dieselbe — "diese Kante ist nach dem Meta-Modell
759
+ * nicht zulaessig" —, nur ist die Bedingung hier eine ueber die MENGE statt ueber das Paar.
760
+ * Eine eigene ID kostete dauerhaft einen Nenner-Anteil, eine Katalogzeile, eine
761
+ * readiness-Zuordnung und einen Golden-File-Eintrag (Gate 6), und ein Leser muesste sie von
762
+ * R-18 unterscheiden, obwohl der naechste Schritt derselbe ist: Kante weg.
763
+ *
764
+ * EIN Befund je Quellknoten, nicht je ueberzaehliger Kante. Bei n Allokationen sind nicht
765
+ * n-1 Kanten "falsch" — es ist EINE Entscheidung offen (welches Modul), und n-1 Befunde
766
+ * wuerden den Zaehler ueber den Nenner-Beitrag treiben, die Fehlmessung aus CR-SM-242.
767
+ */
768
+ function cardinalityViolations(graph) {
769
+ const idx = indexOf(graph);
770
+ const violations = [];
771
+ for (const p of BOUNDED_PATTERNS) {
772
+ const max = maxOccurs(p.cardinality);
773
+ for (const el of idx.elementsOfType(p.source)) {
774
+ const matching = idx.out(el.id, p.type).filter(t => idx.typeOf(t.target) === p.target);
775
+ if (matching.length <= max)
776
+ continue;
777
+ violations.push({
778
+ rule_id: 'R-18',
779
+ severity: 'error',
780
+ element_id: el.id,
781
+ message: `${el.id} has ${matching.length} ${p.type} traces to ${p.target} — ` +
782
+ `the meta-model allows at most ${max} (${matching.map(t => t.target).join(', ')})`,
783
+ fix_hint: `Keep exactly one ${p.type} trace from ${el.id} to a ${p.target} and delete the others`,
784
+ context: {
785
+ element_type: el.type,
786
+ element_name: el.name,
787
+ existing_traces: matching.map(t => ({ source: t.source, target: t.target, type: t.type })),
788
+ },
789
+ });
790
+ }
791
+ }
792
+ return violations;
741
793
  }
742
794
  // ---------------------------------------------------------------------------
743
795
  // R-19: Runnable TEST binding (CR-GC-205 Item 4) — a TEST that is not explicitly
@@ -32,26 +32,45 @@ export function uc02HasActor(graph) {
32
32
  // ueber Elemente und Traces), dann je UC nur noch Mitgliedschaft pruefen. Dieselbe Aussage,
33
33
  // dieselben Befunde in derselben Reihenfolge — die Iteration ueber `graph.elements` bleibt
34
34
  // die aeussere Schleife.
35
+ // CR-SM-266 D1/D4: die Regel prueft ERREICHBARKEIT statt Adjazenz.
36
+ //
37
+ // Bis hierher suchte sie einen io-Ziel-Treffer direkt am UC oder an dessen compose-Kind —
38
+ // also genau die beiden Kanten `ACTOR -io-> UC` und (ueber die FCHAIN) den Direktweg. Beide
39
+ // Patterns sind mit D1/D4 entfallen; die Regel waere damit UNERFUELLBAR geworden: sie haette
40
+ // einen Zustand verlangt, den R-18 im selben Lauf als ungueltige Kante ablehnt — der
41
+ // gegenlaeufige Fall aus Gate 4 des Grammatik-Reviews, und fuer den Nutzer eine Sackgasse
42
+ // statt eines Fehlers.
43
+ //
44
+ // Der tragende Pfad ist jetzt der einzige: `ACTOR -io-> FLOW -io-> FUNC`, FUNC in einer
45
+ // FCHAIN des UC. Zwei Sprungweiten (Actor→FLOW, FLOW→FUNC), danach Mengen-Mitgliedschaft —
46
+ // die CR-SM-261-Optimierung bleibt damit erhalten (kein Scan je UC, alles ueber den Index
47
+ // aus CR-SM-264), und die Iteration ueber `elementsOfType('UC')` bleibt die aeussere
48
+ // Schleife, also ist die Befundreihenfolge weiter die Graph-Reihenfolge (Gate 5).
35
49
  const idx = indexOf(graph);
36
50
  const actorIds = idx.idsOfType('ACTOR');
37
- const actorIoTargets = new Set(idx.tracesOfType('io').filter(t => actorIds.has(t.source)).map(t => t.target));
38
- const composeTargets = new Map();
39
- for (const t of idx.tracesOfType('compose')) {
40
- const list = composeTargets.get(t.source);
41
- if (list)
42
- list.push(t.target);
43
- else
44
- composeTargets.set(t.source, [t.target]);
51
+ // Schritt 1: die FLOWs, die ein ACTOR speist.
52
+ const actorFlows = new Set(idx.tracesOfType('io').filter(t => actorIds.has(t.source)).map(t => t.target));
53
+ // Schritt 2: die FUNCs, die an einem solchen FLOW haengen — in BEIDE Richtungen. Ein UC ist
54
+ // auch dann an einen Actor angebunden, wenn die Kette IHM etwas liefert (FUNC -io-> FLOW
55
+ // -io-> ACTOR), nicht nur wenn sie von ihm getriggert wird. Die alte Fassung sah nur die
56
+ // Trigger-Richtung, weil `ACTOR -io-> UC` nur so herum existierte.
57
+ const actorFacingFuncs = new Set();
58
+ for (const t of idx.tracesOfType('io')) {
59
+ if (actorFlows.has(t.source))
60
+ actorFacingFuncs.add(t.target);
61
+ if (actorIds.has(t.target) && idx.typeOf(t.source) === 'FLOW') {
62
+ for (const producer of idx.in(t.source, 'io'))
63
+ actorFacingFuncs.add(producer.source);
64
+ }
45
65
  }
46
66
  return idx.elementsOfType('UC')
47
- .filter(uc => !actorIoTargets.has(uc.id) &&
48
- !(composeTargets.get(uc.id) ?? []).some(target => actorIoTargets.has(target)))
67
+ .filter(uc => !idx.out(uc.id, 'compose').some(chain => idx.out(chain.target, 'compose').some(member => actorFacingFuncs.has(member.target))))
49
68
  .map(uc => ({
50
69
  rule_id: 'UC-02',
51
70
  severity: 'error',
52
71
  element_id: uc.id,
53
- message: `${uc.id} has no ACTOR with io trace`,
54
- fix_hint: 'Link an ACTOR via io trace to this UC or its FCHAIN',
72
+ message: `${uc.id} is not reachable from any ACTOR`,
73
+ fix_hint: 'Wire an ACTOR to a FLOW that feeds (or is fed by) a FUNC in one of this UC\'s function chains',
55
74
  context: { element_type: uc.type, element_name: uc.name },
56
75
  }));
57
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/contracts",
3
- "version": "6.3.0",
3
+ "version": "9.1.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",