@sigloch/contracts 6.3.0 → 10.0.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 (41) hide show
  1. package/dist/se/ao-rules.d.ts +12 -12
  2. package/dist/se/ao-rules.js +88 -225
  3. package/dist/se/conformance-rules.d.ts +28 -0
  4. package/dist/se/conformance-rules.js +40 -17
  5. package/dist/se/cr-quality-rules.js +75 -58
  6. package/dist/se/evaluate-all.d.ts +4 -2
  7. package/dist/se/evaluate-all.js +40 -24
  8. package/dist/se/fchain-quality-rules.d.ts +0 -1
  9. package/dist/se/fchain-quality-rules.js +0 -44
  10. package/dist/se/flat-graph.d.ts +9 -1
  11. package/dist/se/flat-graph.js +15 -6
  12. package/dist/se/format-e-parser.d.ts +14 -2
  13. package/dist/se/format-e-parser.js +33 -9
  14. package/dist/se/grammar-snapshot.d.ts +10 -7
  15. package/dist/se/grammar-snapshot.js +172 -27
  16. package/dist/se/index.d.ts +4 -3
  17. package/dist/se/index.js +4 -3
  18. package/dist/se/meta-model.d.ts +99 -7
  19. package/dist/se/meta-model.js +155 -14
  20. package/dist/se/metric-rules.d.ts +20 -1
  21. package/dist/se/metric-rules.js +97 -58
  22. package/dist/se/module-crossings.d.ts +102 -0
  23. package/dist/se/module-crossings.js +196 -0
  24. package/dist/se/near-duplicate-rules.d.ts +16 -24
  25. package/dist/se/near-duplicate-rules.js +21 -92
  26. package/dist/se/ontology.d.ts +32 -47
  27. package/dist/se/ontology.js +56 -13
  28. package/dist/se/policy.d.ts +6 -0
  29. package/dist/se/policy.js +53 -2
  30. package/dist/se/quality-rules.d.ts +18 -0
  31. package/dist/se/quality-rules.js +68 -12
  32. package/dist/se/readiness.d.ts +7 -5
  33. package/dist/se/readiness.js +40 -22
  34. package/dist/se/rules.d.ts +6 -7
  35. package/dist/se/rules.js +358 -128
  36. package/dist/se/schema-quality-rules.d.ts +0 -1
  37. package/dist/se/schema-quality-rules.js +7 -28
  38. package/dist/se/similarity.d.ts +61 -0
  39. package/dist/se/similarity.js +116 -0
  40. package/dist/se/uc-quality-rules.js +31 -12
  41. package/package.json +3 -2
@@ -16,6 +16,5 @@ import type { OntologyGraph } from './ontology.js';
16
16
  import type { RuleDefinition, RuleViolation } from './rules.js';
17
17
  import type { MetricPolicy } from './policy.js';
18
18
  export declare function sc02IsReferenced(graph: OntologyGraph): RuleViolation[];
19
- export declare function sc04FlowHasSchema(graph: OntologyGraph): RuleViolation[];
20
19
  export declare const SC_RULES: RuleDefinition[];
21
20
  export declare function evaluateSCRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
@@ -20,40 +20,19 @@ export function sc02IsReferenced(graph) {
20
20
  }));
21
21
  }
22
22
  // ---------------------------------------------------------------------------
23
- // SC-04: FLOW must reference a SCHEMA (CR-SM-226) the sharp per-FLOW
24
- // inverse of SC-02. SC-02 catches an orphan SCHEMA (nobody uses it); SC-04
25
- // catches a FLOW with no data contract at all — until now only caught
26
- // loosely, around the SC-02-adjacent CDR completeness leg, never as its own
27
- // rule.
23
+ // SC-04 (FLOW must reference a SCHEMA, CR-SM-226) ist mit CR-SM-271 Teil 2 ersatzlos
24
+ // entfallen: die Untergrenze ist Grammatik geworden (`FLOW -relation-> SCHEMA [1..1]`,
25
+ // meta-model.ts REQUIRED_PATTERNS) und meldet als error aus dem R-18-Kardinalitaets-Bein
26
+ // (rules.ts cardinalityViolations) Grammatik ersetzt Regel, kein Regel-Loch.
28
27
  // ---------------------------------------------------------------------------
29
- export function sc04FlowHasSchema(graph) {
30
- const schemas = graph.elements.filter(e => e.type === 'SCHEMA');
31
- return graph.elements
32
- .filter(e => e.type === 'FLOW')
33
- .filter(f => !graph.traces.some(t => t.source === f.id && t.type === 'relation' &&
34
- graph.elements.some(e => e.id === t.target && e.type === 'SCHEMA')))
35
- .map(f => ({
36
- rule_id: 'SC-04',
37
- severity: 'warning',
38
- element_id: f.id,
39
- message: `${f.id} has no SCHEMA binding`,
40
- fix_hint: 'Link a SCHEMA via relation trace to define this FLOW\'s data contract',
41
- context: {
42
- element_type: f.type,
43
- element_name: f.name,
44
- candidate_targets: schemas.map(s => ({ id: s.id, type: s.type, name: s.name })),
45
- },
46
- }));
47
- }
48
28
  // ---------------------------------------------------------------------------
49
29
  // Aggregated array & convenience runner
50
30
  // ---------------------------------------------------------------------------
51
31
  export const SC_RULES = [
52
- // Die Asymmetrie ist gewollt (CR-SM-242): SC-02 iteriert ueber SCHEMA („wird dieses SCHEMA
53
- // benutzt"), SC-04 ueber FLOW („hat dieser FLOW einen Datenvertrag"). Gleiches Thema,
54
- // verschiedene Grundgesamtheit. SC-04 als ['SCHEMA'] zog 46 FLOW-Verstoesse gegen 20 SCHEMA.
32
+ // Die Asymmetrie zur FLOW-Seite ist gewollt (CR-SM-242): SC-02 iteriert ueber SCHEMA
33
+ // („wird dieses SCHEMA benutzt"); die Gegenrichtung je FLOW („hat dieser FLOW einen
34
+ // Datenvertrag") traegt seit CR-SM-271 das R-18-Untergrenzen-Bein.
55
35
  { id: 'SC-02', name: 'Schema referenced by FLOW', severity: 'warning', evaluate: sc02IsReferenced, domain: ['SCHEMA'] },
56
- { id: 'SC-04', name: 'FLOW has SCHEMA binding', severity: 'warning', evaluate: sc04FlowHasSchema, domain: ['FLOW'] },
57
36
  ];
58
37
  // CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
59
38
  // ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * CR-SM-286 — die Ähnlichkeitsrechnung von ND-01/ND-02/BQ-04, dort wo die Regeln stehen.
3
+ *
4
+ * ## Warum sie hierher gehört
5
+ *
6
+ * Bis hierher lebte die Formel als **Prosa** in `near-duplicate-rules.ts` und als **Code** in
7
+ * `graphcode/src/kernel/measure/nd-similarity.ts`, verbunden durch
8
+ * `setND01SimilarityMatrix()` / `setND02SimilarityMatrix()`. Zwei Pakete mussten sich über
9
+ * etwas einig sein, das nur eines von beiden ausführt.
10
+ *
11
+ * Die Naht kostete vier Dinge, alle gemessen:
12
+ *
13
+ * 1. **Fail-open.** Ohne Injektion gaben ND-01/ND-02 `[]` zurück — bei severity `error` und
14
+ * ununterscheidbar von „keine Duplikate". Gemessen: moneyflow trägt 16 ND-01-Befunde, die
15
+ * für JEDEN Konsumenten ausserhalb graphcodes unsichtbar waren (`report:silence`, die
16
+ * Spike-Skripte, jedes künftige Werkzeug). Die Familie hat für „kann nicht urteilen" eine
17
+ * Konvention — `policy.X = null`, `moduleMetrics.instability = null`, beide sagen es
18
+ * ausdrücklich; ND fiel statt dessen nach grün.
19
+ * 2. **Prozessweiter Modulzustand.** `let _nd01Matrix` ist global. graphcode schützte sich mit
20
+ * der Klammer `withNDMatrices(og, run)` und einem `finally`-Reset; jeder andere Aufrufer
21
+ * nicht. Zwei Graphen nacheinander bewertet — und Graph A urteilt über Graph B.
22
+ * 3. **AO-D01 verstärkte es.** Die Regel liegt IM Gate-Katalog und übersprang ihre
23
+ * Overlap-Prüfung, wenn keine Matrix da war („no matrix → assume pass"): eine Gate-Regel,
24
+ * die sich stillschweigend abschwächt.
25
+ * 4. **BQ-04 ist am selben Muster komplett tot** — `setBQ04SimilarityMatrix()` ruft im ganzen
26
+ * Familienbaum niemand (CR-SM-278, heute erneut geprüft).
27
+ *
28
+ * Präzedenz aus demselben Repo: CR-SM-276 hat `moduleCrossings` genau deshalb NACH contracts
29
+ * gelegt — damit zwei Regelköpfe (CR-01, R-04) EINE Definition von „Kreuzung" teilen. Hier ist
30
+ * es dieselbe Bewegung für drei Regelköpfe und eine Definition von „ähnlich".
31
+ *
32
+ * ## Was sich NICHT ändert
33
+ *
34
+ * Formeln, Gewichte und die Schwelle 0,85 sind zeichengleich aus graphcodes Implementierung
35
+ * übernommen. Das ist kein neues Urteil, es ist derselbe Code an der richtigen Stelle.
36
+ *
37
+ * Rein und deterministisch: kein Store, kein Transport, kein Filesystem, kein Zufall, keine
38
+ * Abhängigkeit ausserhalb der Ontologie. Ergebnisse hängen je Graph-Objekt im WeakMap-Cache,
39
+ * wie `moduleCrossings` — die Matrix ist O(n²) und wird je Voll-Lauf mehrfach gebraucht.
40
+ */
41
+ import type { OntologyGraph } from './ontology.js';
42
+ /** Wort-Token >= 3 Zeichen, lowercase, Unicode-Buchstaben/Ziffern. */
43
+ export declare function tokens(s: unknown): Set<string>;
44
+ /** Jaccard-Aehnlichkeit; leer/leer = 1 (identisch leer). */
45
+ export declare function jaccard(a: ReadonlySet<string>, b: ReadonlySet<string>): number;
46
+ export interface SimilarityMatrix {
47
+ /** Element-uids in kanonischer Ordnung — der Index in `matrix`. */
48
+ readonly ids: readonly string[];
49
+ /** Symmetrisch, Diagonale 1. */
50
+ readonly matrix: readonly (readonly number[])[];
51
+ }
52
+ /** FUNC-Aehnlichkeit je Graph (ND-01). */
53
+ export declare const funcSimilarity: (graph: OntologyGraph) => SimilarityMatrix;
54
+ /** SCHEMA-Aehnlichkeit je Graph (ND-02, AO-D01). */
55
+ export declare const schemaSimilarity: (graph: OntologyGraph) => SimilarityMatrix;
56
+ /** Paare ueber der Schwelle, kanonisch geordnet (i < j) — die eine Schleife fuer alle drei Regeln. */
57
+ export declare function pairsAbove(sim: SimilarityMatrix, threshold: number): Array<{
58
+ a: string;
59
+ b: string;
60
+ similarity: number;
61
+ }>;
@@ -0,0 +1,116 @@
1
+ import { isValidTrace } from './meta-model.js';
2
+ /** Wort-Token >= 3 Zeichen, lowercase, Unicode-Buchstaben/Ziffern. */
3
+ export function tokens(s) {
4
+ const text = typeof s === 'string' ? s : '';
5
+ return new Set(text
6
+ .toLowerCase()
7
+ .split(/[^\p{L}\p{N}]+/u)
8
+ .filter(w => w.length >= 3));
9
+ }
10
+ /** Jaccard-Aehnlichkeit; leer/leer = 1 (identisch leer). */
11
+ export function jaccard(a, b) {
12
+ if (a.size === 0 && b.size === 0)
13
+ return 1;
14
+ let inter = 0;
15
+ for (const x of a)
16
+ if (b.has(x))
17
+ inter += 1;
18
+ const union = a.size + b.size - inter;
19
+ return union === 0 ? 1 : inter / union;
20
+ }
21
+ /**
22
+ * Partner-uids eines Elements ueber LEGALE Traces der genannten Typen.
23
+ *
24
+ * CR-SM-286: der Legalitaetsfilter ist nicht Kosmetik. Ohne ihn aenderte eine grammatikwidrige
25
+ * Kante das Urteil: `se-rule-pair-legality.test.ts` (Pruefung A, CR-SM-278) meldete **35**
26
+ * Faelle, in denen ND-02 erst durch eine injizierte illegale Kante auf SCHEMA anschlug —
27
+ * `ACTOR -compose-> SCHEMA`, `CR -io-> SCHEMA`, `FLOW -compose-> SCHEMA` und so fort. Eine
28
+ * Regel, deren Verdikt an einer Kante haengt, die R-18 als `error` ablehnt, urteilt ueber einen
29
+ * Graphen, den es nicht geben darf.
30
+ */
31
+ function partners(graph, id, traceTypes) {
32
+ const byType = new Map(graph.elements.map(e => [e.id, e.type]));
33
+ const out = new Set();
34
+ for (const t of graph.traces) {
35
+ if (!traceTypes.includes(t.type))
36
+ continue;
37
+ if (t.source !== id && t.target !== id)
38
+ continue;
39
+ const src = byType.get(t.source);
40
+ const tgt = byType.get(t.target);
41
+ if (!src || !tgt)
42
+ continue; // haengende Kante -> R-08, nicht hier
43
+ if (!isValidTrace({ source: src, target: tgt, type: t.type }))
44
+ continue;
45
+ out.add(t.source === id ? t.target : t.source);
46
+ }
47
+ return out;
48
+ }
49
+ /** Erstes Wort des Namens als Verb-Naeherung (FUNC-Namen: Verb-Objekt). */
50
+ function firstWord(name) {
51
+ const text = typeof name === 'string' ? name : '';
52
+ return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).find(w => w.length > 0) ?? '';
53
+ }
54
+ const byId = (a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
55
+ /** Feld-Menge eines SCHEMA: `attributes.fields` (Array), sonst Beschreibungs-Token. */
56
+ function schemaFields(el) {
57
+ const fields = el.attributes?.fields;
58
+ if (Array.isArray(fields) && fields.length > 0) {
59
+ return new Set(fields.map(f => String(typeof f === 'object' && f !== null ? (f.name ?? JSON.stringify(f)) : f).toLowerCase()));
60
+ }
61
+ return tokens(el.description);
62
+ }
63
+ const CACHE = new WeakMap();
64
+ function build(graph) {
65
+ // ND-01: 0.35*descr_jaccard + 0.25*verb_match + 0.25*io_topology + 0.15*req_overlap
66
+ const funcs = graph.elements.filter(e => e.type === 'FUNC').sort(byId);
67
+ const fDescr = funcs.map(f => tokens(f.description));
68
+ const fVerb = funcs.map(f => firstWord(f.name));
69
+ const fIo = funcs.map(f => partners(graph, f.id, ['io']));
70
+ const fReq = funcs.map(f => new Set(graph.traces.filter(t => t.type === 'satisfy' && t.source === f.id).map(t => t.target)));
71
+ const func = {
72
+ ids: funcs.map(f => f.id),
73
+ matrix: funcs.map((_, i) => funcs.map((_, j) => i === j ? 1
74
+ : 0.35 * jaccard(fDescr[i], fDescr[j])
75
+ + 0.25 * (fVerb[i] !== '' && fVerb[i] === fVerb[j] ? 1 : 0)
76
+ + 0.25 * jaccard(fIo[i], fIo[j])
77
+ + 0.15 * jaccard(fReq[i], fReq[j]))),
78
+ };
79
+ // ND-02: 0.50*field_jaccard + 0.30*descr_jaccard + 0.20*usage_overlap
80
+ const schemas = graph.elements.filter(e => e.type === 'SCHEMA').sort(byId);
81
+ const sFields = schemas.map(schemaFields);
82
+ const sDescr = schemas.map(s => tokens(s.description));
83
+ const sUse = schemas.map(s => partners(graph, s.id, ['relation', 'io', 'compose']));
84
+ const schema = {
85
+ ids: schemas.map(s => s.id),
86
+ matrix: schemas.map((_, i) => schemas.map((_, j) => i === j ? 1
87
+ : 0.5 * jaccard(sFields[i], sFields[j])
88
+ + 0.3 * jaccard(sDescr[i], sDescr[j])
89
+ + 0.2 * jaccard(sUse[i], sUse[j]))),
90
+ };
91
+ return { func, schema };
92
+ }
93
+ function similarities(graph) {
94
+ let s = CACHE.get(graph);
95
+ if (s === undefined) {
96
+ s = build(graph);
97
+ CACHE.set(graph, s);
98
+ }
99
+ return s;
100
+ }
101
+ /** FUNC-Aehnlichkeit je Graph (ND-01). */
102
+ export const funcSimilarity = (graph) => similarities(graph).func;
103
+ /** SCHEMA-Aehnlichkeit je Graph (ND-02, AO-D01). */
104
+ export const schemaSimilarity = (graph) => similarities(graph).schema;
105
+ /** Paare ueber der Schwelle, kanonisch geordnet (i < j) — die eine Schleife fuer alle drei Regeln. */
106
+ export function pairsAbove(sim, threshold) {
107
+ const out = [];
108
+ for (let i = 0; i < sim.ids.length; i++) {
109
+ for (let j = i + 1; j < sim.ids.length; j++) {
110
+ const s = sim.matrix[i][j];
111
+ if (s >= threshold)
112
+ out.push({ a: sim.ids[i], b: sim.ids[j], similarity: s });
113
+ }
114
+ }
115
+ return out;
116
+ }
@@ -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": "10.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,7 +29,8 @@
29
29
  "check:grammar": "node scripts/check-grammar-version.mjs",
30
30
  "grammar:snapshot": "SE_WRITE_GRAMMAR_SNAPSHOT=1 vitest run tests/unit/se-grammar-invariant.test.ts",
31
31
  "prepublishOnly": "npm run build && npm run test",
32
- "grammar:measure": "SE_MEASURE=1 vitest run tests/unit/se-grammar-measure.test.ts"
32
+ "grammar:measure": "SE_MEASURE=1 vitest run tests/unit/se-grammar-measure.test.ts",
33
+ "report:silence": "node scripts/rules-silence-report.mjs"
33
34
  },
34
35
  "dependencies": {
35
36
  "zod": "^4.3.6"