@sigloch/contracts 0.7.0 → 1.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.
- package/dist/se/conformance-rules.d.ts +2 -2
- package/dist/se/conformance-rules.js +34 -31
- package/dist/se/element-uid.d.ts +59 -0
- package/dist/se/element-uid.js +74 -0
- package/dist/se/evaluate-all.js +3 -1
- package/dist/se/format-e-parser.d.ts +37 -5
- package/dist/se/format-e-parser.js +147 -74
- package/dist/se/index.d.ts +3 -3
- package/dist/se/index.js +3 -3
- package/dist/se/meta-model.js +1 -1
- package/dist/se/metric-rules.d.ts +5 -5
- package/dist/se/metric-rules.js +72 -25
- package/dist/se/ontology.d.ts +17 -30
- package/dist/se/ontology.js +25 -33
- package/dist/se/readiness.js +5 -3
- package/dist/se/rules.js +98 -20
- package/dist/se/schema-quality-rules.d.ts +12 -4
- package/dist/se/schema-quality-rules.js +0 -43
- package/package.json +1 -1
- package/dist/se/semantic-id.d.ts +0 -30
- package/dist/se/semantic-id.js +0 -90
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format E Parser — Graph mutations from compact text format.
|
|
3
|
+
* SE-relevant subset: nodes (+/-/~) and edges (+/-), no chat-canvas or views.
|
|
4
|
+
*
|
|
5
|
+
* CR-SM-216 (Format-E v2): the element type comes from the `### <TYPE>` section a node
|
|
6
|
+
* is declared under, never from the spelling of its id. The old id-derived typing made
|
|
7
|
+
* every consumer with a different id convention fail *silently* — aimpro CR-230 lost a
|
|
8
|
+
* whole graph that way (`TYPE-slug` ids rejected, result empty, no error).
|
|
9
|
+
*
|
|
10
|
+
* @sigloch/contracts/se
|
|
11
|
+
*/
|
|
12
|
+
import { ElementType } from './ontology.js';
|
|
1
13
|
import { isValidTrace } from './meta-model.js';
|
|
2
|
-
import { isSemanticId, extractFromSemanticId } from './semantic-id.js';
|
|
3
14
|
// ---------------------------------------------------------------------------
|
|
4
15
|
// Extraction
|
|
5
16
|
// ---------------------------------------------------------------------------
|
|
@@ -21,19 +32,48 @@ const OP_PREFIX = {
|
|
|
21
32
|
'~': 'update',
|
|
22
33
|
'!': 'strict_add',
|
|
23
34
|
};
|
|
24
|
-
|
|
35
|
+
/**
|
|
36
|
+
* CR-SM-215: the target group is `(.+)` — Format-E allows fan-out
|
|
37
|
+
* `A -x-> B, C, D`, one edge per target. `graph-api-core`'s codec has always parsed
|
|
38
|
+
* it; this parser rejected it as `Invalid edge syntax`, so the same text produced
|
|
39
|
+
* different operations depending on which parser saw it.
|
|
40
|
+
*/
|
|
41
|
+
const EDGE_RE = /^([+\-~!])?\s*(\S+)\s+-(\w+)->\s+(.+?)\s*$/;
|
|
25
42
|
const NODE_RE = /^([+\-~!])?\s*(\S+?)(?:\|(.*))?$/;
|
|
26
43
|
/** CR-147: @key value attribute line (indented, below a node entry). */
|
|
27
44
|
const ATTR_RE = /^\s*@(\w+)\s+(.+)$/;
|
|
45
|
+
/**
|
|
46
|
+
* BOK-CR-026: hydrate a JSON object/array attribute value. The ontology's bindings
|
|
47
|
+
* (`realRef {file,symbol?,lang?}`, `testRef {file,tool,…}`) are objects; kept as raw
|
|
48
|
+
* strings they fail RealRefSchema/TestRefSchema and the element reads as unbound.
|
|
49
|
+
* Only `{…}`/`[…]` are attempted — every other value stays the string it is, and a
|
|
50
|
+
* malformed literal falls back to the string rather than failing the whole parse.
|
|
51
|
+
*/
|
|
52
|
+
function hydrateAttrValue(raw) {
|
|
53
|
+
if (!/^[{[]/.test(raw))
|
|
54
|
+
return raw;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
28
62
|
/** CR-148: Trace-type normalization aliases (source→target→from→to). */
|
|
29
63
|
const TRACE_NORMALIZE = {
|
|
30
64
|
FLOW: { SCHEMA: 'relation' }, // FLOW→SCHEMA io → relation
|
|
31
65
|
};
|
|
66
|
+
/** `### <TYPE>` — the node type section (CR-SM-216). */
|
|
67
|
+
const TYPE_SECTION_RE = /^###\s+([A-Za-z_]+)\s*$/;
|
|
32
68
|
/** Parse a Format E text block into validated operations. */
|
|
33
|
-
export function parseFormatE(input) {
|
|
69
|
+
export function parseFormatE(input, options = {}) {
|
|
34
70
|
const operations = [];
|
|
35
71
|
const errors = [];
|
|
36
72
|
let section = null;
|
|
73
|
+
let currentType = null;
|
|
74
|
+
/** uid → type, from this text's node sections. */
|
|
75
|
+
const declared = new Map();
|
|
76
|
+
const typeOf = (uid) => declared.get(uid) ?? options.resolveType?.(uid);
|
|
37
77
|
for (const rawLine of input.split('\n')) {
|
|
38
78
|
const line = rawLine.trim();
|
|
39
79
|
if (!line || line.startsWith('//') || line.startsWith('#!'))
|
|
@@ -45,7 +85,7 @@ export function parseFormatE(input) {
|
|
|
45
85
|
if (lastOp && (lastOp.type === 'add_node' || lastOp.type === 'update_node' || lastOp.type === 'strict_add_node')) {
|
|
46
86
|
if (!lastOp.attributes)
|
|
47
87
|
lastOp.attributes = {};
|
|
48
|
-
lastOp.attributes[attrMatch[1]] = attrMatch[2].trim();
|
|
88
|
+
lastOp.attributes[attrMatch[1]] = hydrateAttrValue(attrMatch[2].trim());
|
|
49
89
|
}
|
|
50
90
|
else {
|
|
51
91
|
errors.push(`@attribute line without preceding node: "${line}"`);
|
|
@@ -55,49 +95,64 @@ export function parseFormatE(input) {
|
|
|
55
95
|
// Section headers
|
|
56
96
|
if (/^##\s*nodes?\s*$/i.test(line)) {
|
|
57
97
|
section = 'nodes';
|
|
98
|
+
currentType = null;
|
|
58
99
|
continue;
|
|
59
100
|
}
|
|
60
101
|
if (/^##\s*edges?\s*$/i.test(line)) {
|
|
61
102
|
section = 'edges';
|
|
103
|
+
currentType = null;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// CR-SM-216: `### <TYPE>` carries the element type for the nodes below it.
|
|
107
|
+
const typeSection = TYPE_SECTION_RE.exec(line);
|
|
108
|
+
if (typeSection && section === 'nodes') {
|
|
109
|
+
const parsed = ElementType.safeParse(typeSection[1]);
|
|
110
|
+
if (parsed.success) {
|
|
111
|
+
currentType = parsed.data;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
currentType = null;
|
|
115
|
+
errors.push(`Unknown element type section: "### ${typeSection[1]}"`);
|
|
116
|
+
}
|
|
62
117
|
continue;
|
|
63
118
|
}
|
|
64
119
|
// Skip other markdown headers
|
|
65
120
|
if (line.startsWith('#'))
|
|
66
121
|
continue;
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
122
|
+
// Edge: detect by arrow in the structural part only — before the description
|
|
123
|
+
// pipe. Node descriptions may legitimately contain '->' ("FUNC->FUNC compose");
|
|
124
|
+
// edges carry no pipe, so splitting on '|' disambiguates (CR-GC-247). Since
|
|
125
|
+
// CR-SM-215 widened the target group to `(.+?)`, this guard is what keeps a
|
|
126
|
+
// multi-word node description from being read as an edge.
|
|
127
|
+
const isEdgeLine = line.split('|', 1)[0].includes('->');
|
|
128
|
+
if (isEdgeLine || section === 'edges') {
|
|
129
|
+
const edgeMatch = isEdgeLine ? EDGE_RE.exec(line) : null;
|
|
70
130
|
if (edgeMatch) {
|
|
71
|
-
parseEdge(edgeMatch, operations, errors);
|
|
131
|
+
parseEdge(edgeMatch, typeOf, operations, errors);
|
|
72
132
|
}
|
|
73
133
|
else {
|
|
74
134
|
errors.push(`Invalid edge line: "${line}"`);
|
|
75
135
|
}
|
|
76
136
|
continue;
|
|
77
137
|
}
|
|
78
|
-
//
|
|
138
|
+
// Node
|
|
79
139
|
if (section === 'nodes') {
|
|
80
140
|
const nodeMatch = NODE_RE.exec(line);
|
|
81
|
-
if (nodeMatch) {
|
|
82
|
-
|
|
141
|
+
if (!nodeMatch) {
|
|
142
|
+
errors.push(`Invalid node line: "${line}"`);
|
|
143
|
+
}
|
|
144
|
+
else if (!currentType) {
|
|
145
|
+
// CR-SM-216: no type section, no type. Guessing one from the id is what
|
|
146
|
+
// CR-230 punished; an error is the point.
|
|
147
|
+
errors.push(`Node "${nodeMatch[2]}" is not under a "### <TYPE>" section`);
|
|
83
148
|
}
|
|
84
149
|
else {
|
|
85
|
-
|
|
150
|
+
declared.set(nodeMatch[2], currentType);
|
|
151
|
+
parseNode(nodeMatch, currentType, operations, errors);
|
|
86
152
|
}
|
|
87
153
|
continue;
|
|
88
154
|
}
|
|
89
|
-
//
|
|
90
|
-
const autoEdge = EDGE_RE.exec(line);
|
|
91
|
-
if (autoEdge) {
|
|
92
|
-
parseEdge(autoEdge, operations, errors);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const autoNode = NODE_RE.exec(line);
|
|
96
|
-
if (autoNode && isSemanticId(autoNode[2])) {
|
|
97
|
-
parseNode(autoNode, operations, errors);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
// Unknown line
|
|
155
|
+
// Unknown line (edges are handled above, nodes need their section)
|
|
101
156
|
if (line.length > 0)
|
|
102
157
|
errors.push(`Unrecognized line: "${line}"`);
|
|
103
158
|
}
|
|
@@ -111,60 +166,40 @@ function normalizeTraceType(srcType, tgtType, traceType) {
|
|
|
111
166
|
}
|
|
112
167
|
return traceType;
|
|
113
168
|
}
|
|
114
|
-
function parseNode(m, ops, errors) {
|
|
169
|
+
function parseNode(m, elementType, ops, errors) {
|
|
115
170
|
const opChar = m[1] || '+';
|
|
116
171
|
const id = m[2];
|
|
117
172
|
const descr = m[3]?.trim();
|
|
118
173
|
const action = OP_PREFIX[opChar] ?? 'add';
|
|
119
|
-
if (!
|
|
120
|
-
errors.push(`
|
|
174
|
+
if (!id) {
|
|
175
|
+
errors.push(`Node line without an id: "${m[0]}"`);
|
|
121
176
|
return;
|
|
122
177
|
}
|
|
123
178
|
if (action === 'remove') {
|
|
124
179
|
ops.push({ type: 'remove_node', semanticId: id });
|
|
125
180
|
}
|
|
126
181
|
else if (action === 'update') {
|
|
127
|
-
ops.push({ type: 'update_node', semanticId: id, description: descr });
|
|
182
|
+
ops.push({ type: 'update_node', semanticId: id, elementType, description: descr });
|
|
128
183
|
}
|
|
129
184
|
else if (action === 'strict_add') {
|
|
130
|
-
ops.push({ type: 'strict_add_node', semanticId: id, description: descr });
|
|
185
|
+
ops.push({ type: 'strict_add_node', semanticId: id, elementType, description: descr });
|
|
131
186
|
}
|
|
132
187
|
else {
|
|
133
|
-
ops.push({ type: 'add_node', semanticId: id, description: descr });
|
|
188
|
+
ops.push({ type: 'add_node', semanticId: id, elementType, description: descr });
|
|
134
189
|
}
|
|
135
190
|
}
|
|
136
|
-
function parseEdge(m, ops, errors) {
|
|
191
|
+
function parseEdge(m, typeOf, ops, errors) {
|
|
137
192
|
const opChar = m[1] || '+';
|
|
138
193
|
const sourceId = m[2];
|
|
139
194
|
const traceType = m[3];
|
|
140
|
-
const targetId = m[4];
|
|
141
195
|
const action = OP_PREFIX[opChar] ?? 'add';
|
|
142
|
-
if (!isSemanticId(sourceId)) {
|
|
143
|
-
errors.push(`Invalid source SemanticId: "${sourceId}"`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (!isSemanticId(targetId)) {
|
|
147
|
-
errors.push(`Invalid target SemanticId: "${targetId}"`);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
196
|
if (!VALID_TRACE_TYPES.has(traceType)) {
|
|
151
197
|
errors.push(`Invalid trace type: "${traceType}"`);
|
|
152
198
|
return;
|
|
153
199
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const src = extractFromSemanticId(sourceId);
|
|
158
|
-
const tgt = extractFromSemanticId(targetId);
|
|
159
|
-
// CR-148: Normalize before meta-model check (e.g. FLOW→SCHEMA io → relation)
|
|
160
|
-
resolvedTraceType = normalizeTraceType(src.type, tgt.type, traceType);
|
|
161
|
-
if (!isValidTrace({ source: src.type, target: tgt.type, type: resolvedTraceType })) {
|
|
162
|
-
errors.push(`Meta-model violation: ${src.type} -${traceType}-> ${tgt.type} is not valid`);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
errors.push(`Cannot extract types from edge: ${sourceId} -${traceType}-> ${targetId}`);
|
|
200
|
+
const srcType = typeOf(sourceId);
|
|
201
|
+
if (!srcType) {
|
|
202
|
+
errors.push(`Cannot resolve type of "${sourceId}" — not declared under a "### <TYPE>" section and no resolveType provided`);
|
|
168
203
|
return;
|
|
169
204
|
}
|
|
170
205
|
const edgeType = action === 'remove'
|
|
@@ -172,33 +207,71 @@ function parseEdge(m, ops, errors) {
|
|
|
172
207
|
: action === 'strict_add'
|
|
173
208
|
? 'strict_add_edge'
|
|
174
209
|
: 'add_edge';
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
// CR-SM-215: 1:n fan-out — `A -x-> B, C` is n independent edges. Validation runs
|
|
211
|
+
// per target (like `graph-api-core`'s codec), so one bad target does not discard
|
|
212
|
+
// its siblings.
|
|
213
|
+
const targets = m[4].split(',').map(t => t.trim()).filter(Boolean);
|
|
214
|
+
for (const targetId of targets) {
|
|
215
|
+
const tgtType = typeOf(targetId);
|
|
216
|
+
if (!tgtType) {
|
|
217
|
+
errors.push(`Cannot resolve type of "${targetId}" — not declared under a "### <TYPE>" section and no resolveType provided`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// CR-148: Normalize before the meta-model check (e.g. FLOW→SCHEMA io → relation)
|
|
221
|
+
const resolvedTraceType = normalizeTraceType(srcType, tgtType, traceType);
|
|
222
|
+
if (!isValidTrace({ source: srcType, target: tgtType, type: resolvedTraceType })) {
|
|
223
|
+
errors.push(`Meta-model violation: ${srcType} -${traceType}-> ${tgtType} is not valid`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
ops.push({
|
|
227
|
+
type: edgeType,
|
|
228
|
+
semanticId: `${sourceId}->${targetId}`,
|
|
229
|
+
sourceId,
|
|
230
|
+
targetId,
|
|
231
|
+
traceType: resolvedTraceType,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
182
234
|
}
|
|
183
235
|
// ---------------------------------------------------------------------------
|
|
184
236
|
// Serializer
|
|
185
237
|
// ---------------------------------------------------------------------------
|
|
186
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* Serialize an OntologyGraph to compact Format E text.
|
|
240
|
+
*
|
|
241
|
+
* CR-SM-216: nodes are written under `### <TYPE>` sections — the type is declared once
|
|
242
|
+
* per section instead of once per id. Measured on the graphcode SSOT graph (369
|
|
243
|
+
* elements), 12 section headers cost ~48 tokens where a per-node type attribute would
|
|
244
|
+
* have cost ~1476.
|
|
245
|
+
*/
|
|
187
246
|
export function serializeToFormatE(graph) {
|
|
188
247
|
const lines = [];
|
|
189
|
-
// Nodes
|
|
190
|
-
|
|
248
|
+
// Nodes, grouped by type
|
|
249
|
+
const modelingElements = graph.elements.filter(el => el.type !== 'SESSION'); // skip audit sessions
|
|
250
|
+
if (modelingElements.length > 0) {
|
|
191
251
|
lines.push('## Nodes');
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
252
|
+
const byType = new Map();
|
|
253
|
+
for (const el of modelingElements) {
|
|
254
|
+
const group = byType.get(el.type);
|
|
255
|
+
if (group)
|
|
256
|
+
group.push(el);
|
|
257
|
+
else
|
|
258
|
+
byType.set(el.type, [el]);
|
|
259
|
+
}
|
|
260
|
+
for (const type of [...byType.keys()].sort()) {
|
|
261
|
+
lines.push(`### ${type}`);
|
|
262
|
+
for (const el of byType.get(type) ?? []) {
|
|
263
|
+
const descr = el.description ? `|${el.description}` : '';
|
|
264
|
+
lines.push(`+ ${el.id}${descr}`);
|
|
265
|
+
// CR-147: Serialize known attributes
|
|
266
|
+
if (el.attributes) {
|
|
267
|
+
for (const [k, v] of Object.entries(el.attributes)) {
|
|
268
|
+
if (v == null)
|
|
269
|
+
continue;
|
|
270
|
+
// BOK-CR-026: objects/arrays as JSON — String({}) collapses a realRef/testRef
|
|
271
|
+
// binding to "[object Object]" and loses it on the next parse.
|
|
272
|
+
const text = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
273
|
+
if (text.length > 0)
|
|
274
|
+
lines.push(` @${k} ${text}`);
|
|
202
275
|
}
|
|
203
276
|
}
|
|
204
277
|
}
|
package/dist/se/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Single source of truth for SE ontology schemas across all projects.
|
|
4
4
|
*/
|
|
5
5
|
/** Ontology schema version (element types + trace types). */
|
|
6
|
-
export declare const ONTOLOGY_VERSION = "
|
|
6
|
+
export declare const ONTOLOGY_VERSION = "4.0.0";
|
|
7
7
|
/** Rules engine version (validation rules incl. RC conformance). */
|
|
8
|
-
export declare const RULES_VERSION = "2.
|
|
8
|
+
export declare const RULES_VERSION = "2.20.0";
|
|
9
9
|
/** Meta-model version (trace pattern constraints + format-e parser). */
|
|
10
10
|
export declare const META_MODEL_VERSION = "1.4.0";
|
|
11
11
|
export * from './ontology.js';
|
|
@@ -24,5 +24,5 @@ export * from './quality-rules.js';
|
|
|
24
24
|
export * from './evaluate-all.js';
|
|
25
25
|
export * from './readiness.js';
|
|
26
26
|
export * from './meta-model.js';
|
|
27
|
-
export * from './
|
|
27
|
+
export * from './element-uid.js';
|
|
28
28
|
export * from './format-e-parser.js';
|
package/dist/se/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Single source of truth for SE ontology schemas across all projects.
|
|
4
4
|
*/
|
|
5
5
|
/** Ontology schema version (element types + trace types). */
|
|
6
|
-
export const ONTOLOGY_VERSION = '
|
|
6
|
+
export const ONTOLOGY_VERSION = '4.0.0'; // BREAKING: `SemanticId` (`Name.TypeAbbr.Counter`) deleted, `ElementUid` (`<TYPE>-<slug>`) is the family canon — the old canon was used by no production graph of the family while 626 of 1145 elements already carried TYPE-slug (CR-SM-217); realRef unifies codeRef+schemaRef (+physical-MOD CAD ref), symbol optional; testRef stays separate (CR-228 C); +RepoRelativePathSchema on testRef/realRef .file — no absolute/`..` paths (CR-GC-255)
|
|
7
7
|
/** Rules engine version (validation rules incl. RC conformance). */
|
|
8
|
-
export const RULES_VERSION = '2.
|
|
8
|
+
export const RULES_VERSION = '2.20.0'; // +RD-04 decomposition breadth (>11 children per level → warning) and MT-03 recalibrated to FLOW-transitive connection pairs — counting raw io traces reported internal=0 on every real SE graph (15/15 false positives), because the meta-model routes FUNC↔FUNC through FLOW; 'RD-' added to the se profile prefixes, where RD-01..04 were silently missing (CR-SM-221); -SC-01/-SC-03 deleted (BOK-CR-026): `realRef` is the single SCHEMA binding truth (R-26 presence, RC-03/RC-04 resolution); the legacy `zodDefinition`/`sourceFile`/`sourceExport` attributes are gone from every producer, SC_RULES = [SC-02]; R-20/R-26/RC-01/RC-03/RC-04 read realRef (unified codeRef+schemaRef); +R-27 physical-MOD realRef presence (CR-228 C); -R-24/R-25 REQ→MOD allocation rules deleted (CR-228 A: REQ→MOD allocate no longer a valid pattern, R-18 flags residual edges); NFR-01 budget target split physical→MOD / behavioral→FCHAIN; RULE_TO_DIMENSION completeness (R-18..R-23/R-26/MS-03/CR-R04 mapped, no advisory fall-through) (CR-228 B/D); +BQ-01/02/04/06/07 base-quality rules promoted from aimpro (K2-b, completes rule consolidation); +ND-01/02 near-duplicate + AO-D01/D03/CR-01/RT-01/PH-01/CA-01/IO-01 architecture rules promoted from aimpro (K2-b); +CR-R01..04/MS-03 change-request rules promoted from aimpro (K2-b); +FM-01..03/NFR-01 FMEA + VR-01/CL-01 view rules promoted from aimpro (K2-b); +MT-01..03 architecture metrics promoted from aimpro (K2-b); +SC-01..03/UC-01..06/FC-01..03 quality rules promoted from aimpro (K2-a); +RC-05 cross-module import drift (CR-212); +R-26/RC-03/RC-04 schemaRef (CR-211); +R-22..R-25/R-10/R-20 (CR-201/202/208/209/210)
|
|
9
9
|
/** Meta-model version (trace pattern constraints + format-e parser). */
|
|
10
10
|
export const META_MODEL_VERSION = '1.4.0'; // -REQ→MOD allocate pattern removed (CR-228 A); +FUNC→FUNC compose (blackbox function decomposition)
|
|
11
11
|
export * from './ontology.js';
|
|
@@ -24,5 +24,5 @@ export * from './quality-rules.js';
|
|
|
24
24
|
export * from './evaluate-all.js';
|
|
25
25
|
export * from './readiness.js';
|
|
26
26
|
export * from './meta-model.js';
|
|
27
|
-
export * from './
|
|
27
|
+
export * from './element-uid.js';
|
|
28
28
|
export * from './format-e-parser.js';
|
package/dist/se/meta-model.js
CHANGED
|
@@ -8,7 +8,7 @@ export const TRACE_PATTERNS = [
|
|
|
8
8
|
{ source: 'UC', target: 'FCHAIN', type: 'compose', cardinality: '1..*', description: 'UC behavioral scenarios' },
|
|
9
9
|
{ source: 'UC', target: 'REQ', type: 'compose', cardinality: '1..*', description: 'UC functional/non-functional requirements' },
|
|
10
10
|
{ source: 'FCHAIN', target: 'FUNC', type: 'compose', cardinality: '1..*', description: 'Functions participating in chain' },
|
|
11
|
-
{ source: 'FUNC', target: 'FUNC', type: 'compose', cardinality: '0..*', description: 'Function (blackbox architecture block) decomposes into sub-functions one level deeper; leaf FUNCs carry
|
|
11
|
+
{ source: 'FUNC', target: 'FUNC', type: 'compose', cardinality: '0..*', description: 'Function (blackbox architecture block) decomposes into sub-functions one level deeper; leaf FUNCs carry realRef, the parent is realized by its children' },
|
|
12
12
|
{ source: 'REQ', target: 'REQ', type: 'compose', cardinality: '0..*', description: 'Requirement decomposes into sub-requirements (incl. mitigation)' },
|
|
13
13
|
// ── io (data exchange, always through FLOW) ──
|
|
14
14
|
{ source: 'ACTOR', target: 'FLOW', type: 'io', description: 'Actor triggers/receives via flow' },
|
|
@@ -19,11 +19,11 @@ export declare function mt01Instability(graph: OntologyGraph): RuleViolation[];
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function mt02Lcom4(graph: OntologyGraph): RuleViolation[];
|
|
21
21
|
/**
|
|
22
|
-
* MT-03: Allocation Cohesion (CR-191
|
|
23
|
-
* cohesion =
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* If
|
|
22
|
+
* MT-03: Allocation Cohesion (CR-191 reformulated, CR-SM-221 recalibrated).
|
|
23
|
+
* cohesion = internal / (internal + external), over FLOW-transitive connection pairs.
|
|
24
|
+
* internal = both endpoints allocated to this module.
|
|
25
|
+
* external = exactly one endpoint allocated to this module.
|
|
26
|
+
* If external === 0 → cohesion = 100% → OK.
|
|
27
27
|
*/
|
|
28
28
|
export declare function mt03AllocationCohesion(graph: OntologyGraph): RuleViolation[];
|
|
29
29
|
export declare const MT_RULES: readonly [{
|
package/dist/se/metric-rules.js
CHANGED
|
@@ -151,48 +151,95 @@ export function mt02Lcom4(graph) {
|
|
|
151
151
|
return violations;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
154
|
+
* CR-SM-221: connection pairs between non-FLOW elements, FLOW-transitive.
|
|
155
|
+
*
|
|
156
|
+
* The SE meta-model routes FUNC↔FUNC communication through FLOW nodes
|
|
157
|
+
* (`FUNC —io→ FLOW —io→ FUNC`); a direct FUNC→FUNC io trace is the exception, not
|
|
158
|
+
* the rule. Counting raw io traces therefore found *zero* internal edges on every
|
|
159
|
+
* real graph — measured on graphcode (369 elements), graph-view-edit (257) and the
|
|
160
|
+
* family graph (638): 15 of 15 MODs reported internal=0, i.e. 15 false positives and
|
|
161
|
+
* no discriminating power at all.
|
|
162
|
+
*
|
|
163
|
+
* A pair counts once, no matter how many flows connect it: the metric asks "do these
|
|
164
|
+
* two talk to each other", not "how often".
|
|
165
|
+
*/
|
|
166
|
+
function connectionPairs(graph) {
|
|
167
|
+
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
168
|
+
const pairs = new Set();
|
|
169
|
+
const add = (a, b) => {
|
|
170
|
+
if (a !== b)
|
|
171
|
+
pairs.add(a < b ? `${a}|${b}` : `${b}|${a}`);
|
|
172
|
+
};
|
|
173
|
+
const intoFlow = new Map(); // flow → producers
|
|
174
|
+
const outOfFlow = new Map(); // flow → consumers
|
|
175
|
+
const push = (m, k, v) => {
|
|
176
|
+
const list = m.get(k);
|
|
177
|
+
if (list)
|
|
178
|
+
list.push(v);
|
|
179
|
+
else
|
|
180
|
+
m.set(k, [v]);
|
|
181
|
+
};
|
|
182
|
+
for (const t of graph.traces) {
|
|
183
|
+
if (t.type !== 'io')
|
|
184
|
+
continue;
|
|
185
|
+
const srcIsFlow = typeOf.get(t.source) === 'FLOW';
|
|
186
|
+
const tgtIsFlow = typeOf.get(t.target) === 'FLOW';
|
|
187
|
+
if (srcIsFlow && tgtIsFlow)
|
|
188
|
+
continue; // FLOW→FLOW carries no endpoint
|
|
189
|
+
if (!srcIsFlow && !tgtIsFlow) {
|
|
190
|
+
add(t.source, t.target);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (tgtIsFlow)
|
|
194
|
+
push(intoFlow, t.target, t.source);
|
|
195
|
+
else
|
|
196
|
+
push(outOfFlow, t.source, t.target);
|
|
197
|
+
}
|
|
198
|
+
for (const [flow, producers] of intoFlow) {
|
|
199
|
+
for (const consumer of outOfFlow.get(flow) ?? []) {
|
|
200
|
+
for (const producer of producers)
|
|
201
|
+
add(producer, consumer);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return pairs;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* MT-03: Allocation Cohesion (CR-191 reformulated, CR-SM-221 recalibrated).
|
|
208
|
+
* cohesion = internal / (internal + external), over FLOW-transitive connection pairs.
|
|
209
|
+
* internal = both endpoints allocated to this module.
|
|
210
|
+
* external = exactly one endpoint allocated to this module.
|
|
211
|
+
* If external === 0 → cohesion = 100% → OK.
|
|
159
212
|
*/
|
|
160
213
|
export function mt03AllocationCohesion(graph) {
|
|
161
214
|
const violations = [];
|
|
162
215
|
const mods = graph.elements.filter(e => e.type === 'MOD');
|
|
163
216
|
const COHESION_THRESHOLD = 0.8;
|
|
217
|
+
const pairs = [...connectionPairs(graph)].map(p => p.split('|'));
|
|
164
218
|
for (const mod of mods) {
|
|
165
219
|
const allocTraces = graph.traces.filter(t => t.type === 'allocate' && t.target === mod.id);
|
|
166
220
|
const funcIds = new Set(allocTraces.map(t => t.source));
|
|
167
221
|
if (funcIds.size < 2)
|
|
168
222
|
continue;
|
|
169
|
-
let
|
|
170
|
-
let
|
|
171
|
-
for (const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
else if (srcIn || tgtIn) {
|
|
180
|
-
externalFlows++;
|
|
181
|
-
}
|
|
223
|
+
let internal = 0;
|
|
224
|
+
let external = 0;
|
|
225
|
+
for (const [a, b] of pairs) {
|
|
226
|
+
const aIn = funcIds.has(a);
|
|
227
|
+
const bIn = funcIds.has(b);
|
|
228
|
+
if (aIn && bIn)
|
|
229
|
+
internal++;
|
|
230
|
+
else if (aIn || bIn)
|
|
231
|
+
external++;
|
|
182
232
|
}
|
|
183
|
-
// No external
|
|
184
|
-
if (
|
|
185
|
-
continue;
|
|
186
|
-
const total = internalFlows + externalFlows;
|
|
187
|
-
if (total === 0)
|
|
233
|
+
// No external connections → cohesion = 100% → OK
|
|
234
|
+
if (external === 0)
|
|
188
235
|
continue;
|
|
189
|
-
const cohesion =
|
|
236
|
+
const cohesion = internal / (internal + external);
|
|
190
237
|
if (cohesion < COHESION_THRESHOLD) {
|
|
191
238
|
violations.push({
|
|
192
239
|
rule_id: 'MT-03',
|
|
193
240
|
severity: 'info',
|
|
194
241
|
element_id: mod.id,
|
|
195
|
-
message: `${mod.name} allocation cohesion ${Math.round(cohesion * 100)}% (<${COHESION_THRESHOLD * 100}%). internal=${
|
|
242
|
+
message: `${mod.name} allocation cohesion ${Math.round(cohesion * 100)}% (<${COHESION_THRESHOLD * 100}%). internal=${internal}, external=${external}`,
|
|
196
243
|
});
|
|
197
244
|
}
|
|
198
245
|
}
|
package/dist/se/ontology.d.ts
CHANGED
|
@@ -110,40 +110,27 @@ export declare const TestRefSchema: z.ZodObject<{
|
|
|
110
110
|
}, z.core.$strip>;
|
|
111
111
|
export type TestRef = z.infer<typeof TestRefSchema>;
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* - `
|
|
120
|
-
* - `
|
|
121
|
-
*
|
|
122
|
-
*
|
|
113
|
+
* Realization binding for an element (CR-228, ontology bump 3.9.0) — unifies the
|
|
114
|
+
* byte-identical former `codeRef` (FUNC → code symbol) and `schemaRef` (SCHEMA →
|
|
115
|
+
* Zod export), and extends to MOD (physical part → CAD/geometry artefact). The
|
|
116
|
+
* *type* of the pointing element disambiguates what kind of realization it is:
|
|
117
|
+
* FUNC→code, SCHEMA→Zod-def, physical MOD→CAD. TEST keeps its own `testRef`
|
|
118
|
+
* (a TEST is not just located but *executed* — the runner is the extra value).
|
|
119
|
+
* - `file` : realization file path, e.g. `src/harness.ts`, `part.step`.
|
|
120
|
+
* - `symbol` : the realizing symbol (function/class/Zod export). Optional — a
|
|
121
|
+
* geometry artefact (CAD) has no symbol; file-exists is the binding.
|
|
122
|
+
* - `lang` : optional language/format id (default `ts`) — selects the
|
|
123
|
+
* conformance engine, so the binding is language-agnostic.
|
|
124
|
+
* Stored under `OntologyElement.attributes.realRef` (additive, opt-in validation).
|
|
125
|
+
* The free-text SCHEMA `contract` attribute stays a human description, not the
|
|
126
|
+
* conformance basis: RC-01/RC-03 resolve this binding, RC-04 checks it is parsed.
|
|
123
127
|
*/
|
|
124
|
-
export declare const
|
|
128
|
+
export declare const RealRefSchema: z.ZodObject<{
|
|
125
129
|
file: z.ZodString;
|
|
126
|
-
symbol: z.ZodString
|
|
130
|
+
symbol: z.ZodOptional<z.ZodString>;
|
|
127
131
|
lang: z.ZodOptional<z.ZodString>;
|
|
128
132
|
}, z.core.$strip>;
|
|
129
|
-
export type
|
|
130
|
-
/**
|
|
131
|
-
* Binding of a SCHEMA node to the Zod schema that defines it (CR-211), analogous
|
|
132
|
-
* to CodeRefSchema for FUNC:
|
|
133
|
-
* - `file` : the source file declaring the Zod schema, e.g. `src/se/ontology.ts`.
|
|
134
|
-
* - `symbol` : the exported schema symbol, e.g. `CodeRefSchema`.
|
|
135
|
-
* - `lang` : optional language id (default `ts`).
|
|
136
|
-
* Stored under `OntologyElement.attributes.schemaRef`. The free-text `contract`
|
|
137
|
-
* attribute stays as a human description but is no longer the conformance basis:
|
|
138
|
-
* RC-03 resolves this binding to a declared export, RC-04 checks it is parsed at
|
|
139
|
-
* the modelled interface.
|
|
140
|
-
*/
|
|
141
|
-
export declare const SchemaRefSchema: z.ZodObject<{
|
|
142
|
-
file: z.ZodString;
|
|
143
|
-
symbol: z.ZodString;
|
|
144
|
-
lang: z.ZodOptional<z.ZodString>;
|
|
145
|
-
}, z.core.$strip>;
|
|
146
|
-
export type SchemaRef = z.infer<typeof SchemaRefSchema>;
|
|
133
|
+
export type RealRef = z.infer<typeof RealRefSchema>;
|
|
147
134
|
/**
|
|
148
135
|
* An element (node) in the SE ontology graph.
|
|
149
136
|
* `attributes` holds type-specific properties (e.g. FUNC.safety_relevant).
|