@sigloch/contracts 0.7.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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/dist/harness/index.d.ts +185 -0
  3. package/dist/harness/index.js +185 -0
  4. package/dist/index.d.ts +13 -0
  5. package/dist/index.js +14 -0
  6. package/dist/se/ao-rules.d.ts +59 -0
  7. package/dist/se/ao-rules.js +341 -0
  8. package/dist/se/conformance-rules.d.ts +64 -0
  9. package/dist/se/conformance-rules.js +364 -0
  10. package/dist/se/cr-quality-rules.d.ts +8 -0
  11. package/dist/se/cr-quality-rules.js +141 -0
  12. package/dist/se/evaluate-all.d.ts +17 -0
  13. package/dist/se/evaluate-all.js +50 -0
  14. package/dist/se/fchain-quality-rules.d.ts +10 -0
  15. package/dist/se/fchain-quality-rules.js +105 -0
  16. package/dist/se/fmea-rules.d.ts +17 -0
  17. package/dist/se/fmea-rules.js +137 -0
  18. package/dist/se/format-e-parser.d.ts +28 -0
  19. package/dist/se/format-e-parser.js +217 -0
  20. package/dist/se/index.d.ts +28 -0
  21. package/dist/se/index.js +28 -0
  22. package/dist/se/meta-model.d.ts +26 -0
  23. package/dist/se/meta-model.js +60 -0
  24. package/dist/se/metric-rules.d.ts +45 -0
  25. package/dist/se/metric-rules.js +208 -0
  26. package/dist/se/near-duplicate-rules.d.ts +44 -0
  27. package/dist/se/near-duplicate-rules.js +106 -0
  28. package/dist/se/ontology.d.ts +327 -0
  29. package/dist/se/ontology.js +216 -0
  30. package/dist/se/quality-rules.d.ts +28 -0
  31. package/dist/se/quality-rules.js +206 -0
  32. package/dist/se/readiness.d.ts +62 -0
  33. package/dist/se/readiness.js +79 -0
  34. package/dist/se/rules.d.ts +159 -0
  35. package/dist/se/rules.js +854 -0
  36. package/dist/se/schema-quality-rules.d.ts +11 -0
  37. package/dist/se/schema-quality-rules.js +73 -0
  38. package/dist/se/semantic-id.d.ts +30 -0
  39. package/dist/se/semantic-id.js +90 -0
  40. package/dist/se/uc-quality-rules.d.ts +13 -0
  41. package/dist/se/uc-quality-rules.js +123 -0
  42. package/dist/se/view-rules.d.ts +11 -0
  43. package/dist/se/view-rules.js +56 -0
  44. package/package.json +51 -0
@@ -0,0 +1,105 @@
1
+ // ---------------------------------------------------------------------------
2
+ // FC-01: FCHAIN must have Actor boundary (input or output via FLOW→ACTOR io)
3
+ // ---------------------------------------------------------------------------
4
+ export function fc01ActorBoundary(graph) {
5
+ return graph.elements
6
+ .filter(e => e.type === 'FCHAIN')
7
+ .filter(fc => {
8
+ // Get FUNCs in this FCHAIN via compose
9
+ const funcIds = graph.traces
10
+ .filter(t => t.source === fc.id && t.type === 'compose')
11
+ .map(t => t.target);
12
+ // Check if any FUNC has io trace involving an ACTOR (via FLOW)
13
+ const hasActorIO = funcIds.some(fid => graph.traces.some(t => {
14
+ if (t.type !== 'io')
15
+ return false;
16
+ // FUNC→FLOW or FLOW→FUNC
17
+ const flowId = t.source === fid ? t.target : (t.target === fid ? t.source : null);
18
+ if (!flowId)
19
+ return false;
20
+ // FLOW→ACTOR or ACTOR→FLOW
21
+ return graph.traces.some(ft => ft.type === 'io' &&
22
+ ((ft.source === flowId && graph.elements.some(e => e.id === ft.target && e.type === 'ACTOR')) ||
23
+ (ft.target === flowId && graph.elements.some(e => e.id === ft.source && e.type === 'ACTOR'))));
24
+ }));
25
+ // Also check direct ACTOR→UC io on the parent UC
26
+ const parentUC = graph.traces.find(t => t.type === 'compose' && t.target === fc.id &&
27
+ graph.elements.some(e => e.id === t.source && e.type === 'UC'));
28
+ const hasDirectActorIO = parentUC && graph.traces.some(t => t.type === 'io' &&
29
+ ((t.target === parentUC.source && graph.elements.some(e => e.id === t.source && e.type === 'ACTOR')) ||
30
+ (t.source === parentUC.source && graph.elements.some(e => e.id === t.target && e.type === 'ACTOR'))));
31
+ return !hasActorIO && !hasDirectActorIO;
32
+ })
33
+ .map(fc => ({
34
+ rule_id: 'FC-01',
35
+ severity: 'warning',
36
+ element_id: fc.id,
37
+ message: `${fc.id} has no actor boundary (no ACTOR io connection)`,
38
+ fix_hint: 'Ensure at least one FUNC in the chain connects to an ACTOR via FLOW io',
39
+ context: { element_type: fc.type, element_name: fc.name },
40
+ }));
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // FC-02: Leaf UC (no UC→compose→UC) must have at least one FCHAIN
44
+ // ---------------------------------------------------------------------------
45
+ export function fc02LeafUcHasFchain(graph) {
46
+ return graph.elements
47
+ .filter(e => e.type === 'UC')
48
+ .filter(uc => {
49
+ // Leaf = no compose→UC children
50
+ const hasUCChild = graph.traces.some(t => t.source === uc.id && t.type === 'compose' &&
51
+ graph.elements.some(e => e.id === t.target && e.type === 'UC'));
52
+ if (hasUCChild)
53
+ return false;
54
+ // Must have compose→FCHAIN
55
+ return !graph.traces.some(t => t.source === uc.id && t.type === 'compose' &&
56
+ graph.elements.some(e => e.id === t.target && e.type === 'FCHAIN'));
57
+ })
58
+ .map(uc => ({
59
+ rule_id: 'FC-02',
60
+ severity: 'warning',
61
+ element_id: uc.id,
62
+ message: `Leaf UC ${uc.id} has no FCHAIN scenario`,
63
+ fix_hint: 'Add a FCHAIN via compose trace for behavioral specification',
64
+ context: { element_type: uc.type, element_name: uc.name },
65
+ }));
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // FC-03: FUNC within FCHAIN should not compose other FUNC (flat chain)
69
+ // ---------------------------------------------------------------------------
70
+ export function fc03FchainFlat(graph) {
71
+ const violations = [];
72
+ const fchains = graph.elements.filter(e => e.type === 'FCHAIN');
73
+ for (const fc of fchains) {
74
+ const funcIds = graph.traces
75
+ .filter(t => t.source === fc.id && t.type === 'compose')
76
+ .map(t => t.target)
77
+ .filter(id => graph.elements.some(e => e.id === id && e.type === 'FUNC'));
78
+ for (const fid of funcIds) {
79
+ const nestedCompose = graph.traces.filter(t => t.source === fid && t.type === 'compose' &&
80
+ graph.elements.some(e => e.id === t.target && e.type === 'FUNC'));
81
+ if (nestedCompose.length > 0) {
82
+ violations.push({
83
+ rule_id: 'FC-03',
84
+ severity: 'warning',
85
+ element_id: fid,
86
+ message: `${fid} in ${fc.id} has nested FUNC compose (should be flat)`,
87
+ fix_hint: 'Move nested functions to FCHAIN level (flat composition)',
88
+ context: { element_type: 'FUNC', element_name: graph.elements.find(e => e.id === fid)?.name ?? fid },
89
+ });
90
+ }
91
+ }
92
+ }
93
+ return violations;
94
+ }
95
+ // ---------------------------------------------------------------------------
96
+ // Aggregated
97
+ // ---------------------------------------------------------------------------
98
+ export const FC_RULES = [
99
+ { id: 'FC-01', name: 'FCHAIN has actor boundary', severity: 'warning', evaluate: fc01ActorBoundary },
100
+ { id: 'FC-02', name: 'Leaf UC has FCHAIN', severity: 'warning', evaluate: fc02LeafUcHasFchain },
101
+ { id: 'FC-03', name: 'FCHAIN is flat', severity: 'warning', evaluate: fc03FchainFlat },
102
+ ];
103
+ export function evaluateFCRules(graph) {
104
+ return FC_RULES.flatMap(rule => rule.evaluate(graph));
105
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CR-182: FMEA/FTA Risk Rules + NFR Budget Overshoot Detection.
3
+ * FM-01: Risk-REQ missing FMEA attributes (severity, occurrence, detection).
4
+ * FM-02: Risk-REQ without mitigation (compose→REQ(kinds∋mitigation)).
5
+ * FM-03: High-risk REQ (RPN>100) without passed verification.
6
+ * NFR-01: measured exceeds budget for an NFR dimension. CR-228 splits the target
7
+ * by dimension nature: physical budgets (weight/power/cost) live on the part
8
+ * (MOD); behavioral budgets (timing/memory-throughput) on the FCHAIN/FUNC.
9
+ */
10
+ import type { OntologyGraph } from './ontology.js';
11
+ import type { RuleViolation, RuleDefinition } from './rules.js';
12
+ export declare function fm01MissingFmeaAttributes(graph: OntologyGraph): RuleViolation[];
13
+ export declare function fm02MissingMitigation(graph: OntologyGraph): RuleViolation[];
14
+ export declare function fm03HighRiskUnverified(graph: OntologyGraph): RuleViolation[];
15
+ export declare function nfr01BudgetOvershoot(graph: OntologyGraph): RuleViolation[];
16
+ export declare const FM_RULES: RuleDefinition[];
17
+ export declare function evaluateFMRules(graph: OntologyGraph): RuleViolation[];
@@ -0,0 +1,137 @@
1
+ const PHYSICAL_BUDGET_PAIRS = [
2
+ ['costBudget', 'measuredCost', 'cost'],
3
+ ['weightBudgetKg', 'measuredWeightKg', 'weight (kg)'],
4
+ ['powerBudgetW', 'measuredPowerW', 'power (W)'],
5
+ ];
6
+ const BEHAVIORAL_BUDGET_PAIRS = [
7
+ ['timingBudgetMs', 'measuredMs', 'timing (ms)'],
8
+ ['memoryBudgetMb', 'measuredMemoryMb', 'memory (MB)'],
9
+ ];
10
+ const PHYSICAL_BUDGET_TYPES = ['MOD'];
11
+ const BEHAVIORAL_BUDGET_TYPES = ['FUNC', 'FCHAIN'];
12
+ // ---------------------------------------------------------------------------
13
+ // FM-01: Risk-REQ without FMEA attributes
14
+ // ---------------------------------------------------------------------------
15
+ export function fm01MissingFmeaAttributes(graph) {
16
+ return graph.elements
17
+ .filter(e => e.type === 'REQ' && e.kinds?.includes('risk'))
18
+ .filter(e => {
19
+ const a = e.attributes ?? {};
20
+ return a['severity'] == null || a['occurrence'] == null || a['detection'] == null;
21
+ })
22
+ .map(e => {
23
+ const missing = [];
24
+ const a = e.attributes ?? {};
25
+ if (a['severity'] == null)
26
+ missing.push('severity');
27
+ if (a['occurrence'] == null)
28
+ missing.push('occurrence');
29
+ if (a['detection'] == null)
30
+ missing.push('detection');
31
+ return {
32
+ rule_id: 'FM-01',
33
+ severity: 'warning',
34
+ element_id: e.id,
35
+ message: `${e.id} is a risk REQ missing FMEA attributes: ${missing.join(', ')}`,
36
+ fix_hint: 'Add severity, occurrence, and detection ratings (1-10) to attributes',
37
+ };
38
+ });
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // FM-02: Risk-REQ without mitigation
42
+ // ---------------------------------------------------------------------------
43
+ export function fm02MissingMitigation(graph) {
44
+ const riskReqs = graph.elements.filter(e => e.type === 'REQ' && e.kinds?.includes('risk'));
45
+ return riskReqs
46
+ .filter(riskReq => {
47
+ // Check: compose trace from riskReq to a REQ with kinds∋mitigation
48
+ return !graph.traces.some(t => t.source === riskReq.id &&
49
+ t.type === 'compose' &&
50
+ graph.elements.some(el => el.id === t.target && el.type === 'REQ' && el.kinds?.includes('mitigation')));
51
+ })
52
+ .map(e => ({
53
+ rule_id: 'FM-02',
54
+ severity: 'warning',
55
+ element_id: e.id,
56
+ message: `${e.id} is a risk REQ without a mitigation REQ (compose→REQ[mitigation])`,
57
+ fix_hint: 'Create a mitigation REQ and link via compose trace',
58
+ }));
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // FM-03: High-risk REQ (RPN > 100) without passed verification
62
+ // ---------------------------------------------------------------------------
63
+ export function fm03HighRiskUnverified(graph) {
64
+ const riskReqs = graph.elements.filter(e => e.type === 'REQ' && e.kinds?.includes('risk'));
65
+ return riskReqs
66
+ .filter(riskReq => {
67
+ const a = riskReq.attributes ?? {};
68
+ const s = Number(a['severity']);
69
+ const o = Number(a['occurrence']);
70
+ const d = Number(a['detection']);
71
+ if (isNaN(s) || isNaN(o) || isNaN(d))
72
+ return false; // can't compute RPN → skip
73
+ const rpn = s * o * d;
74
+ if (rpn <= 100)
75
+ return false;
76
+ // Check: TEST→verify→riskReq with testResult=passed
77
+ const hasPassedTest = graph.traces.some(t => t.type === 'verify' &&
78
+ t.target === riskReq.id &&
79
+ graph.elements.some(el => el.id === t.source && el.type === 'TEST' && el.attributes?.['testResult'] === 'passed'));
80
+ return !hasPassedTest;
81
+ })
82
+ .map(e => {
83
+ const a = e.attributes ?? {};
84
+ const rpn = Number(a['severity']) * Number(a['occurrence']) * Number(a['detection']);
85
+ return {
86
+ rule_id: 'FM-03',
87
+ severity: 'error',
88
+ element_id: e.id,
89
+ message: `${e.id} has RPN ${rpn} (>100) without passed test verification`,
90
+ fix_hint: 'Add a TEST with testResult=passed and verify trace to this risk REQ',
91
+ };
92
+ });
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // NFR-01: Budget overshoot detection
96
+ // ---------------------------------------------------------------------------
97
+ export function nfr01BudgetOvershoot(graph) {
98
+ const violations = [];
99
+ const check = (types, pairs) => {
100
+ for (const el of graph.elements) {
101
+ if (!types.includes(el.type))
102
+ continue;
103
+ const a = el.attributes ?? {};
104
+ for (const [budgetKey, measuredKey, label] of pairs) {
105
+ const budget = Number(a[budgetKey]);
106
+ const measured = Number(a[measuredKey]);
107
+ if (isNaN(budget) || isNaN(measured))
108
+ continue;
109
+ if (measured > budget) {
110
+ violations.push({
111
+ rule_id: 'NFR-01',
112
+ severity: 'warning',
113
+ element_id: el.id,
114
+ message: `${el.id} ${label} over budget: ${measured} > ${budget}`,
115
+ fix_hint: `Reduce ${measuredKey} or increase ${budgetKey}`,
116
+ });
117
+ }
118
+ }
119
+ }
120
+ };
121
+ // Physical budgets on the part (MOD); behavioral budgets on the behavior (FCHAIN/FUNC).
122
+ check(PHYSICAL_BUDGET_TYPES, PHYSICAL_BUDGET_PAIRS);
123
+ check(BEHAVIORAL_BUDGET_TYPES, BEHAVIORAL_BUDGET_PAIRS);
124
+ return violations;
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Aggregated array & convenience runner
128
+ // ---------------------------------------------------------------------------
129
+ export const FM_RULES = [
130
+ { id: 'FM-01', name: 'RiskReqFmeaAttributes', severity: 'warning', evaluate: fm01MissingFmeaAttributes },
131
+ { id: 'FM-02', name: 'RiskReqMitigation', severity: 'warning', evaluate: fm02MissingMitigation },
132
+ { id: 'FM-03', name: 'HighRiskVerification', severity: 'error', evaluate: fm03HighRiskUnverified },
133
+ { id: 'NFR-01', name: 'BudgetOvershoot', severity: 'warning', evaluate: nfr01BudgetOvershoot },
134
+ ];
135
+ export function evaluateFMRules(graph) {
136
+ return FM_RULES.flatMap(rule => rule.evaluate(graph));
137
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Format E Parser — Graph mutations from compact text format.
3
+ * SE-relevant subset: nodes (+/-/~) and edges (+/-), no chat-canvas or views.
4
+ * ~150 LOC, no external dependencies beyond @sigloch/contracts/se.
5
+ *
6
+ * @sigloch/contracts/se
7
+ */
8
+ import type { TraceType, OntologyGraph } from './ontology.js';
9
+ export interface FormatEOperation {
10
+ type: 'add_node' | 'remove_node' | 'update_node' | 'add_edge' | 'remove_edge' | 'strict_add_node' | 'strict_add_edge';
11
+ semanticId: string;
12
+ description?: string;
13
+ /** CR-147: Parsed @key value attributes from lines below the node entry. */
14
+ attributes?: Record<string, string>;
15
+ sourceId?: string;
16
+ targetId?: string;
17
+ traceType?: TraceType;
18
+ }
19
+ export interface FormatEDiff {
20
+ operations: FormatEOperation[];
21
+ errors: string[];
22
+ }
23
+ /** Extract a ```format-e block from LLM output. Returns null if not found. */
24
+ export declare function extractFormatE(llmOutput: string): string | null;
25
+ /** Parse a Format E text block into validated operations. */
26
+ export declare function parseFormatE(input: string): FormatEDiff;
27
+ /** Serialize an OntologyGraph to compact Format E text. */
28
+ export declare function serializeToFormatE(graph: OntologyGraph): string;
@@ -0,0 +1,217 @@
1
+ import { isValidTrace } from './meta-model.js';
2
+ import { isSemanticId, extractFromSemanticId } from './semantic-id.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Extraction
5
+ // ---------------------------------------------------------------------------
6
+ const FORMAT_E_FENCE = /```format-e\s*\n([\s\S]*?)```/;
7
+ /** Extract a ```format-e block from LLM output. Returns null if not found. */
8
+ export function extractFormatE(llmOutput) {
9
+ const m = FORMAT_E_FENCE.exec(llmOutput);
10
+ return m ? m[1].trim() : null;
11
+ }
12
+ // ---------------------------------------------------------------------------
13
+ // Parser
14
+ // ---------------------------------------------------------------------------
15
+ const VALID_TRACE_TYPES = new Set([
16
+ 'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation', 'produces',
17
+ ]);
18
+ const OP_PREFIX = {
19
+ '+': 'add',
20
+ '-': 'remove',
21
+ '~': 'update',
22
+ '!': 'strict_add',
23
+ };
24
+ const EDGE_RE = /^([+\-~!])?\s*(\S+)\s+-(\w+)->\s+(\S+)\s*$/;
25
+ const NODE_RE = /^([+\-~!])?\s*(\S+?)(?:\|(.*))?$/;
26
+ /** CR-147: @key value attribute line (indented, below a node entry). */
27
+ const ATTR_RE = /^\s*@(\w+)\s+(.+)$/;
28
+ /** CR-148: Trace-type normalization aliases (source→target→from→to). */
29
+ const TRACE_NORMALIZE = {
30
+ FLOW: { SCHEMA: 'relation' }, // FLOW→SCHEMA io → relation
31
+ };
32
+ /** Parse a Format E text block into validated operations. */
33
+ export function parseFormatE(input) {
34
+ const operations = [];
35
+ const errors = [];
36
+ let section = null;
37
+ for (const rawLine of input.split('\n')) {
38
+ const line = rawLine.trim();
39
+ if (!line || line.startsWith('//') || line.startsWith('#!'))
40
+ continue;
41
+ // CR-147: @attribute lines attach to the last node operation
42
+ const attrMatch = ATTR_RE.exec(line);
43
+ if (attrMatch) {
44
+ const lastOp = operations.length > 0 ? operations[operations.length - 1] : null;
45
+ if (lastOp && (lastOp.type === 'add_node' || lastOp.type === 'update_node' || lastOp.type === 'strict_add_node')) {
46
+ if (!lastOp.attributes)
47
+ lastOp.attributes = {};
48
+ lastOp.attributes[attrMatch[1]] = attrMatch[2].trim();
49
+ }
50
+ else {
51
+ errors.push(`@attribute line without preceding node: "${line}"`);
52
+ }
53
+ continue;
54
+ }
55
+ // Section headers
56
+ if (/^##\s*nodes?\s*$/i.test(line)) {
57
+ section = 'nodes';
58
+ continue;
59
+ }
60
+ if (/^##\s*edges?\s*$/i.test(line)) {
61
+ section = 'edges';
62
+ continue;
63
+ }
64
+ // Skip other markdown headers
65
+ if (line.startsWith('#'))
66
+ continue;
67
+ // Try edge first (more specific pattern)
68
+ const edgeMatch = EDGE_RE.exec(line);
69
+ if (edgeMatch || section === 'edges') {
70
+ if (edgeMatch) {
71
+ parseEdge(edgeMatch, operations, errors);
72
+ }
73
+ else {
74
+ errors.push(`Invalid edge line: "${line}"`);
75
+ }
76
+ continue;
77
+ }
78
+ // Try node
79
+ if (section === 'nodes') {
80
+ const nodeMatch = NODE_RE.exec(line);
81
+ if (nodeMatch) {
82
+ parseNode(nodeMatch, operations, errors);
83
+ }
84
+ else {
85
+ errors.push(`Invalid node line: "${line}"`);
86
+ }
87
+ continue;
88
+ }
89
+ // Outside a section — try to auto-detect
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
101
+ if (line.length > 0)
102
+ errors.push(`Unrecognized line: "${line}"`);
103
+ }
104
+ return { operations, errors };
105
+ }
106
+ /** CR-148: Normalize trace type using alias table. */
107
+ function normalizeTraceType(srcType, tgtType, traceType) {
108
+ const aliases = TRACE_NORMALIZE[srcType];
109
+ if (aliases && aliases[tgtType] && traceType !== aliases[tgtType]) {
110
+ return aliases[tgtType];
111
+ }
112
+ return traceType;
113
+ }
114
+ function parseNode(m, ops, errors) {
115
+ const opChar = m[1] || '+';
116
+ const id = m[2];
117
+ const descr = m[3]?.trim();
118
+ const action = OP_PREFIX[opChar] ?? 'add';
119
+ if (!isSemanticId(id)) {
120
+ errors.push(`Invalid SemanticId: "${id}"`);
121
+ return;
122
+ }
123
+ if (action === 'remove') {
124
+ ops.push({ type: 'remove_node', semanticId: id });
125
+ }
126
+ else if (action === 'update') {
127
+ ops.push({ type: 'update_node', semanticId: id, description: descr });
128
+ }
129
+ else if (action === 'strict_add') {
130
+ ops.push({ type: 'strict_add_node', semanticId: id, description: descr });
131
+ }
132
+ else {
133
+ ops.push({ type: 'add_node', semanticId: id, description: descr });
134
+ }
135
+ }
136
+ function parseEdge(m, ops, errors) {
137
+ const opChar = m[1] || '+';
138
+ const sourceId = m[2];
139
+ const traceType = m[3];
140
+ const targetId = m[4];
141
+ 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
+ if (!VALID_TRACE_TYPES.has(traceType)) {
151
+ errors.push(`Invalid trace type: "${traceType}"`);
152
+ return;
153
+ }
154
+ // Meta-model validation (with CR-148 normalization)
155
+ let resolvedTraceType = traceType;
156
+ try {
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}`);
168
+ return;
169
+ }
170
+ const edgeType = action === 'remove'
171
+ ? 'remove_edge'
172
+ : action === 'strict_add'
173
+ ? 'strict_add_edge'
174
+ : 'add_edge';
175
+ ops.push({
176
+ type: edgeType,
177
+ semanticId: `${sourceId}->${targetId}`,
178
+ sourceId,
179
+ targetId,
180
+ traceType: resolvedTraceType,
181
+ });
182
+ }
183
+ // ---------------------------------------------------------------------------
184
+ // Serializer
185
+ // ---------------------------------------------------------------------------
186
+ /** Serialize an OntologyGraph to compact Format E text. */
187
+ export function serializeToFormatE(graph) {
188
+ const lines = [];
189
+ // Nodes
190
+ if (graph.elements.length > 0) {
191
+ lines.push('## Nodes');
192
+ for (const el of graph.elements) {
193
+ if (el.type === 'SESSION')
194
+ continue; // skip audit sessions
195
+ const descr = el.description ? `|${el.description}` : '';
196
+ lines.push(`+ ${el.id}${descr}`);
197
+ // CR-147: Serialize known attributes
198
+ if (el.attributes) {
199
+ for (const [k, v] of Object.entries(el.attributes)) {
200
+ if (v != null && String(v).length > 0) {
201
+ lines.push(` @${k} ${String(v)}`);
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+ // Edges
208
+ const modelingTraces = graph.traces.filter(t => t.category !== 'audit');
209
+ if (modelingTraces.length > 0) {
210
+ lines.push('');
211
+ lines.push('## Edges');
212
+ for (const t of modelingTraces) {
213
+ lines.push(`+ ${t.source} -${t.type}-> ${t.target}`);
214
+ }
215
+ }
216
+ return lines.join('\n');
217
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @sigloch/contracts/se — SE Ontology + Rules (versions below are the single source of truth).
3
+ * Single source of truth for SE ontology schemas across all projects.
4
+ */
5
+ /** Ontology schema version (element types + trace types). */
6
+ export declare const ONTOLOGY_VERSION = "3.8.0";
7
+ /** Rules engine version (validation rules incl. RC conformance). */
8
+ export declare const RULES_VERSION = "2.17.0";
9
+ /** Meta-model version (trace pattern constraints + format-e parser). */
10
+ export declare const META_MODEL_VERSION = "1.4.0";
11
+ export * from './ontology.js';
12
+ export * from './rules.js';
13
+ export * from './conformance-rules.js';
14
+ export * from './schema-quality-rules.js';
15
+ export * from './uc-quality-rules.js';
16
+ export * from './fchain-quality-rules.js';
17
+ export * from './metric-rules.js';
18
+ export * from './fmea-rules.js';
19
+ export * from './view-rules.js';
20
+ export * from './cr-quality-rules.js';
21
+ export * from './near-duplicate-rules.js';
22
+ export * from './ao-rules.js';
23
+ export * from './quality-rules.js';
24
+ export * from './evaluate-all.js';
25
+ export * from './readiness.js';
26
+ export * from './meta-model.js';
27
+ export * from './semantic-id.js';
28
+ export * from './format-e-parser.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @sigloch/contracts/se — SE Ontology + Rules (versions below are the single source of truth).
3
+ * Single source of truth for SE ontology schemas across all projects.
4
+ */
5
+ /** Ontology schema version (element types + trace types). */
6
+ export const ONTOLOGY_VERSION = '3.8.0'; // +RepoRelativePathSchema on testRef/codeRef/schemaRef .file — no absolute/`..` paths (CR-GC-255)
7
+ /** Rules engine version (validation rules incl. RC conformance). */
8
+ export const RULES_VERSION = '2.17.0'; // -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
+ /** Meta-model version (trace pattern constraints + format-e parser). */
10
+ export const META_MODEL_VERSION = '1.4.0'; // -REQ→MOD allocate pattern removed (CR-228 A); +FUNC→FUNC compose (blackbox function decomposition)
11
+ export * from './ontology.js';
12
+ export * from './rules.js';
13
+ export * from './conformance-rules.js';
14
+ export * from './schema-quality-rules.js';
15
+ export * from './uc-quality-rules.js';
16
+ export * from './fchain-quality-rules.js';
17
+ export * from './metric-rules.js';
18
+ export * from './fmea-rules.js';
19
+ export * from './view-rules.js';
20
+ export * from './cr-quality-rules.js';
21
+ export * from './near-duplicate-rules.js';
22
+ export * from './ao-rules.js';
23
+ export * from './quality-rules.js';
24
+ export * from './evaluate-all.js';
25
+ export * from './readiness.js';
26
+ export * from './meta-model.js';
27
+ export * from './semantic-id.js';
28
+ export * from './format-e-parser.js';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SE Meta-Model — declarative table of valid trace patterns.
3
+ * Single source of truth for which ElementType pairs are valid for which TraceType.
4
+ * @sigloch/contracts/se
5
+ */
6
+ import type { ElementType, TraceType } from './ontology.js';
7
+ export interface TracePattern {
8
+ source: ElementType | '*';
9
+ target: ElementType | '*';
10
+ type: TraceType;
11
+ category?: 'modeling' | 'audit';
12
+ label?: string;
13
+ cardinality?: '1' | '1..*' | '0..*';
14
+ description: string;
15
+ }
16
+ export declare const TRACE_PATTERNS: TracePattern[];
17
+ /**
18
+ * Check if a trace matches a valid pattern in the meta-model.
19
+ * Uses element types (not IDs) for validation.
20
+ */
21
+ export declare function isValidTrace(trace: {
22
+ source: ElementType;
23
+ target: ElementType;
24
+ type: TraceType;
25
+ label?: string;
26
+ }, patterns?: TracePattern[]): boolean;
@@ -0,0 +1,60 @@
1
+ export const TRACE_PATTERNS = [
2
+ // ── compose (parent → child) ──
3
+ { source: 'SYS', target: 'SYS', type: 'compose', cardinality: '0..*', description: 'System decomposes into subsystems' },
4
+ { source: 'SYS', target: 'UC', type: 'compose', cardinality: '1..*', description: 'System decomposes into use cases' },
5
+ { source: 'SYS', target: 'REQ', type: 'compose', cardinality: '0..*', description: 'System-level requirements' },
6
+ { source: 'SYS', target: 'MOD', type: 'compose', cardinality: '0..*', description: 'System decomposes into modules' },
7
+ { source: 'MOD', target: 'MOD', type: 'compose', cardinality: '0..*', description: 'Module contains sub-module (e.g. ACL belongs to BC)' },
8
+ { source: 'UC', target: 'FCHAIN', type: 'compose', cardinality: '1..*', description: 'UC behavioral scenarios' },
9
+ { source: 'UC', target: 'REQ', type: 'compose', cardinality: '1..*', description: 'UC functional/non-functional requirements' },
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 codeRef, the parent is realized by its children' },
12
+ { source: 'REQ', target: 'REQ', type: 'compose', cardinality: '0..*', description: 'Requirement decomposes into sub-requirements (incl. mitigation)' },
13
+ // ── io (data exchange, always through FLOW) ──
14
+ { source: 'ACTOR', target: 'FLOW', type: 'io', description: 'Actor triggers/receives via flow' },
15
+ { source: 'FLOW', target: 'ACTOR', type: 'io', description: 'Flow delivers to actor' },
16
+ { source: 'FUNC', target: 'FLOW', type: 'io', description: 'Function outputs to flow' },
17
+ { source: 'FLOW', target: 'FUNC', type: 'io', description: 'Flow feeds into function' },
18
+ { source: 'ACTOR', target: 'UC', type: 'io', description: 'Actor triggers use case' },
19
+ { source: 'FLOW', target: 'UC', type: 'io', description: 'Flow feeds use case' },
20
+ { source: 'MOD', target: 'MOD', type: 'io', description: 'Module communicates with module (ACL)' },
21
+ // ── satisfy (implementation) ──
22
+ { source: 'FUNC', target: 'REQ', type: 'satisfy', description: 'Function implements requirement' },
23
+ { source: 'FUNC', target: 'UC', type: 'satisfy', description: 'Function implements use case' },
24
+ // CR-154: NFR satisfy — non-functional REQs can be satisfied by chains, modules, or the system
25
+ { source: 'FCHAIN', target: 'REQ', type: 'satisfy', description: 'Function chain satisfies end-to-end NFR (e.g. latency)' },
26
+ { source: 'MOD', target: 'REQ', type: 'satisfy', description: 'Module satisfies budget NFR (e.g. uptime, memory)' },
27
+ { source: 'SYS', target: 'REQ', type: 'satisfy', description: 'System satisfies system-level NFR (e.g. availability)' },
28
+ // ── verify (INCOSE: test verifies requirement) ──
29
+ { source: 'TEST', target: 'REQ', type: 'verify', description: 'Test verifies requirement' },
30
+ // ── allocate (deployment/assignment) ──
31
+ { source: 'FUNC', target: 'MOD', type: 'allocate', description: 'Function deployed in module' },
32
+ { source: 'FLOW', target: 'SCHEMA', type: 'relation', description: 'Flow data format defined by schema' },
33
+ // CR-228: REQ→MOD allocate removed — a MOD does not own a REQ. Physical NFRs use
34
+ // MOD→satisfy→REQ, behavioral NFRs FCHAIN→satisfy→REQ; structural constraints are
35
+ // rules, not REQs. R-18 now flags any REQ→MOD allocate edge as invalid.
36
+ // ── MS (Milestone scope + dependencies, CR-181) ──
37
+ { source: 'MS', target: 'FUNC', type: 'compose', description: 'Milestone includes function' },
38
+ { source: 'MS', target: 'REQ', type: 'compose', description: 'Milestone includes requirement' },
39
+ { source: 'MS', target: 'UC', type: 'compose', description: 'Milestone includes use case' },
40
+ { source: 'MS', target: 'MS', type: 'compose', description: 'Sub-milestone' },
41
+ { source: 'MS', target: 'MS', type: 'relation', label: 'depends-on', description: 'Milestone dependency' },
42
+ { source: 'CR', target: 'MS', type: 'relation', description: 'CR assigned to milestone' },
43
+ // ── CR traceability (CR-155: CR tracks mutated elements) ──
44
+ { source: 'CR', target: 'UC', type: 'relation', description: 'CR affects use case' },
45
+ { source: 'CR', target: 'REQ', type: 'relation', description: 'CR affects requirement' },
46
+ { source: 'CR', target: 'FUNC', type: 'relation', description: 'CR affects function' },
47
+ { source: 'CR', target: 'MOD', type: 'relation', description: 'CR affects module' },
48
+ // ── produces (audit lineage) ──
49
+ { source: 'SESSION', target: '*', type: 'produces', category: 'audit', description: 'Session produced/modified element' },
50
+ ];
51
+ /**
52
+ * Check if a trace matches a valid pattern in the meta-model.
53
+ * Uses element types (not IDs) for validation.
54
+ */
55
+ export function isValidTrace(trace, patterns = TRACE_PATTERNS) {
56
+ return patterns.some(p => (p.source === '*' || p.source === trace.source) &&
57
+ (p.target === '*' || p.target === trace.target) &&
58
+ p.type === trace.type &&
59
+ (!p.label || p.label === trace.label));
60
+ }