@sigloch/contracts 2.0.0 → 3.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/harness/index.d.ts +9 -0
- package/dist/harness/index.js +17 -0
- package/dist/se/analysis-freshness-rules.d.ts +39 -0
- package/dist/se/analysis-freshness-rules.js +44 -0
- package/dist/se/ao-rules.d.ts +1 -1
- package/dist/se/ao-rules.js +12 -10
- package/dist/se/evaluate-all.js +4 -1
- package/dist/se/fchain-quality-rules.d.ts +1 -0
- package/dist/se/fchain-quality-rules.js +43 -0
- package/dist/se/index.d.ts +2 -1
- package/dist/se/index.js +2 -1
- package/dist/se/ontology.d.ts +28 -0
- package/dist/se/ontology.js +20 -0
- package/dist/se/readiness.d.ts +26 -2
- package/dist/se/readiness.js +86 -5
- package/dist/se/rules.js +36 -0
- package/dist/se/schema-quality-rules.d.ts +1 -0
- package/dist/se/schema-quality-rules.js +27 -0
- package/package.json +1 -1
package/dist/harness/index.d.ts
CHANGED
|
@@ -79,6 +79,15 @@ export declare const MutateCommandSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
79
79
|
targetUid: z.ZodString;
|
|
80
80
|
}, z.core.$strip>], "op">;
|
|
81
81
|
export type MutateCommand = z.infer<typeof MutateCommandSchema>;
|
|
82
|
+
export interface OpRisk {
|
|
83
|
+
destructive: boolean;
|
|
84
|
+
structural: boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare const OP_RISK: Record<MutateCommand['op'], OpRisk>;
|
|
87
|
+
/** True iff `op` removes data that is not recoverable by re-running the same op. */
|
|
88
|
+
export declare function isDestructiveOp(op: MutateCommand['op']): boolean;
|
|
89
|
+
/** True iff `op` changes graph topology (nodes/edges), not just attributes. */
|
|
90
|
+
export declare function isStructuralOp(op: MutateCommand['op']): boolean;
|
|
82
91
|
/** Monotone graph revision; incremented by +1 per successful mutate(). */
|
|
83
92
|
export declare const GraphVersionSchema: z.ZodNumber;
|
|
84
93
|
export type GraphVersion = z.infer<typeof GraphVersionSchema>;
|
package/dist/harness/index.js
CHANGED
|
@@ -93,6 +93,23 @@ export const MutateCommandSchema = z.discriminatedUnion('op', [
|
|
|
93
93
|
targetUid: z.string(),
|
|
94
94
|
}),
|
|
95
95
|
]);
|
|
96
|
+
export const OP_RISK = {
|
|
97
|
+
'add-node': { destructive: false, structural: true },
|
|
98
|
+
'update-node': { destructive: false, structural: false },
|
|
99
|
+
'delete-node': { destructive: true, structural: true },
|
|
100
|
+
'add-edge': { destructive: false, structural: true },
|
|
101
|
+
'delete-edge': { destructive: true, structural: true },
|
|
102
|
+
'update-edge': { destructive: false, structural: true },
|
|
103
|
+
'merge-nodes': { destructive: true, structural: true },
|
|
104
|
+
};
|
|
105
|
+
/** True iff `op` removes data that is not recoverable by re-running the same op. */
|
|
106
|
+
export function isDestructiveOp(op) {
|
|
107
|
+
return OP_RISK[op].destructive;
|
|
108
|
+
}
|
|
109
|
+
/** True iff `op` changes graph topology (nodes/edges), not just attributes. */
|
|
110
|
+
export function isStructuralOp(op) {
|
|
111
|
+
return OP_RISK[op].structural;
|
|
112
|
+
}
|
|
96
113
|
// ---------------------------------------------------------------------------
|
|
97
114
|
// CR-199: OCC — graph revision + optional baseVersion on mutate().
|
|
98
115
|
// Schema-only here; revision counter/stale-check/delta live in the harness
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-227: Analysis-Freshness-Legs — the third leg-kind a review gate checks
|
|
3
|
+
* (rule legs, layer-presence legs [R-28], now analysis-freshness legs).
|
|
4
|
+
* Presence/Resolution-Split (wie R-19/R-20/R-26/R-27 vs. RC-01..05): these five
|
|
5
|
+
* rules are the PRÜFBARE FORM only — does a `graphVersion` stamp exist for the
|
|
6
|
+
* artifact, as a graph attribute. They never compare the stamp against the
|
|
7
|
+
* live graphVersion (that staleness/currency check is I/O — a consumer
|
|
8
|
+
* concern, `CreationCurrencyProvider` in `@sigloch/graphcode-client`).
|
|
9
|
+
*/
|
|
10
|
+
import type { OntologyGraph } from './ontology.js';
|
|
11
|
+
import type { RuleViolation } from './rules.js';
|
|
12
|
+
export declare const AF_RULES: readonly [{
|
|
13
|
+
readonly id: "AF-01";
|
|
14
|
+
readonly name: "ConOps freshness stamp present";
|
|
15
|
+
readonly severity: "warning";
|
|
16
|
+
readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
|
|
17
|
+
}, {
|
|
18
|
+
readonly id: "AF-02";
|
|
19
|
+
readonly name: "Trade Study freshness stamp present";
|
|
20
|
+
readonly severity: "warning";
|
|
21
|
+
readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
|
|
22
|
+
}, {
|
|
23
|
+
readonly id: "AF-03";
|
|
24
|
+
readonly name: "Assumption Review freshness stamp present";
|
|
25
|
+
readonly severity: "warning";
|
|
26
|
+
readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
|
|
27
|
+
}, {
|
|
28
|
+
readonly id: "AF-04";
|
|
29
|
+
readonly name: "FMEA freshness stamp present";
|
|
30
|
+
readonly severity: "warning";
|
|
31
|
+
readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
|
|
32
|
+
}, {
|
|
33
|
+
readonly id: "AF-05";
|
|
34
|
+
readonly name: "Implementation Plan freshness stamp present";
|
|
35
|
+
readonly severity: "warning";
|
|
36
|
+
readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
|
|
37
|
+
}];
|
|
38
|
+
/** Evaluate all analysis-freshness-presence rules. */
|
|
39
|
+
export declare function evaluateAFRules(graph: OntologyGraph): RuleViolation[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AnalysisFreshnessStampSchema } from './ontology.js';
|
|
2
|
+
const ARTIFACT_LABEL = {
|
|
3
|
+
conops: 'Concept of Operations',
|
|
4
|
+
trade: 'Trade Study',
|
|
5
|
+
'assumption-review': 'Assumption Review',
|
|
6
|
+
fmea: 'FMEA',
|
|
7
|
+
implplan: 'Implementation Plan',
|
|
8
|
+
};
|
|
9
|
+
/** Presence-check for one analysis artifact's freshness stamp, anchored on SYS (like R-28). */
|
|
10
|
+
function analysisFreshnessPresence(ruleId, artifactId) {
|
|
11
|
+
return (graph) => {
|
|
12
|
+
const sys = graph.elements.find(e => e.type === 'SYS');
|
|
13
|
+
if (!sys)
|
|
14
|
+
return []; // nothing to anchor on yet (same vacuous-complete exemption as R-28)
|
|
15
|
+
const stamp = sys.attributes?.analysisFreshness?.[artifactId];
|
|
16
|
+
if (AnalysisFreshnessStampSchema.safeParse(stamp).success)
|
|
17
|
+
return [];
|
|
18
|
+
const label = ARTIFACT_LABEL[artifactId];
|
|
19
|
+
return [{
|
|
20
|
+
rule_id: ruleId,
|
|
21
|
+
severity: 'warning',
|
|
22
|
+
element_id: sys.id,
|
|
23
|
+
message: `${label} (${artifactId}) has no freshness stamp — was it ever written with a graphVersion stamp?`,
|
|
24
|
+
fix_hint: `Set attributes.analysisFreshness.${artifactId}.graphVersion to the current graphVersion() when writing/refreshing the ${label} artifact`,
|
|
25
|
+
context: { element_type: sys.type, element_name: sys.name },
|
|
26
|
+
}];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const conopsFreshnessPresent = analysisFreshnessPresence('AF-01', 'conops');
|
|
30
|
+
const tradeFreshnessPresent = analysisFreshnessPresence('AF-02', 'trade');
|
|
31
|
+
const assumptionReviewFreshnessPresent = analysisFreshnessPresence('AF-03', 'assumption-review');
|
|
32
|
+
const fmeaFreshnessPresent = analysisFreshnessPresence('AF-04', 'fmea');
|
|
33
|
+
const implplanFreshnessPresent = analysisFreshnessPresence('AF-05', 'implplan');
|
|
34
|
+
export const AF_RULES = [
|
|
35
|
+
{ id: 'AF-01', name: 'ConOps freshness stamp present', severity: 'warning', evaluate: conopsFreshnessPresent },
|
|
36
|
+
{ id: 'AF-02', name: 'Trade Study freshness stamp present', severity: 'warning', evaluate: tradeFreshnessPresent },
|
|
37
|
+
{ id: 'AF-03', name: 'Assumption Review freshness stamp present', severity: 'warning', evaluate: assumptionReviewFreshnessPresent },
|
|
38
|
+
{ id: 'AF-04', name: 'FMEA freshness stamp present', severity: 'warning', evaluate: fmeaFreshnessPresent },
|
|
39
|
+
{ id: 'AF-05', name: 'Implementation Plan freshness stamp present', severity: 'warning', evaluate: implplanFreshnessPresent },
|
|
40
|
+
];
|
|
41
|
+
/** Evaluate all analysis-freshness-presence rules. */
|
|
42
|
+
export function evaluateAFRules(graph) {
|
|
43
|
+
return AF_RULES.flatMap(r => r.evaluate(graph));
|
|
44
|
+
}
|
package/dist/se/ao-rules.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export declare const AO_RULES: readonly [{
|
|
|
52
52
|
readonly evaluate: typeof ca01CapabilityAllocation;
|
|
53
53
|
}, {
|
|
54
54
|
readonly id: "IO-01";
|
|
55
|
-
readonly name: "
|
|
55
|
+
readonly name: "FuncPairIOCompleteness";
|
|
56
56
|
readonly severity: "warning";
|
|
57
57
|
readonly evaluate: typeof io01CrossModuleCompleteness;
|
|
58
58
|
}];
|
package/dist/se/ao-rules.js
CHANGED
|
@@ -256,8 +256,12 @@ export function ca01CapabilityAllocation(graph) {
|
|
|
256
256
|
return violations;
|
|
257
257
|
}
|
|
258
258
|
// ---------------------------------------------------------------------------
|
|
259
|
-
// IO-01:
|
|
260
|
-
// For each FCHAIN, FUNCs in
|
|
259
|
+
// IO-01: FUNC-pair IO completeness (CR-192, extended CR-SM-226)
|
|
260
|
+
// For each FCHAIN with ≥2 FUNCs, every pair of FUNCs in the chain must have a
|
|
261
|
+
// FLOW path between them — not just pairs allocated to different MODs.
|
|
262
|
+
// CR-SM-226 removed the cross-module-only restriction: same-module FUNC
|
|
263
|
+
// pairs need an explicit FLOW just as much (module allocation is an
|
|
264
|
+
// orthogonal concern to whether the interaction itself is wired up).
|
|
261
265
|
// ---------------------------------------------------------------------------
|
|
262
266
|
export function io01CrossModuleCompleteness(graph) {
|
|
263
267
|
const violations = [];
|
|
@@ -270,23 +274,21 @@ export function io01CrossModuleCompleteness(graph) {
|
|
|
270
274
|
.filter(id => graph.elements.some(e => e.id === id && e.type === 'FUNC'));
|
|
271
275
|
if (funcIds.length < 2)
|
|
272
276
|
continue;
|
|
273
|
-
// Map FUNC→MOD via allocate
|
|
277
|
+
// Map FUNC→MOD via allocate (kept for the message context; no longer a filter).
|
|
274
278
|
const funcToMod = new Map();
|
|
275
279
|
for (const fid of funcIds) {
|
|
276
280
|
const allocTrace = graph.traces.find(t => t.source === fid && t.type === 'allocate');
|
|
277
281
|
if (allocTrace)
|
|
278
282
|
funcToMod.set(fid, allocTrace.target);
|
|
279
283
|
}
|
|
280
|
-
// For each pair of FUNCs in
|
|
284
|
+
// For each pair of FUNCs in the chain, check for a FLOW path.
|
|
281
285
|
const checked = new Set();
|
|
282
286
|
for (const fA of funcIds) {
|
|
283
287
|
for (const fB of funcIds) {
|
|
284
288
|
if (fA >= fB)
|
|
285
289
|
continue;
|
|
286
|
-
const modA = funcToMod.get(fA);
|
|
287
|
-
const modB = funcToMod.get(fB);
|
|
288
|
-
if (!modA || !modB || modA === modB)
|
|
289
|
-
continue;
|
|
290
|
+
const modA = funcToMod.get(fA) ?? 'unallocated';
|
|
291
|
+
const modB = funcToMod.get(fB) ?? 'unallocated';
|
|
290
292
|
const pairKey = `${fA}:${fB}`;
|
|
291
293
|
if (checked.has(pairKey))
|
|
292
294
|
continue;
|
|
@@ -314,7 +316,7 @@ export function io01CrossModuleCompleteness(graph) {
|
|
|
314
316
|
severity: 'warning',
|
|
315
317
|
element_id: fA,
|
|
316
318
|
message: `${fA} (${modA}) and ${fB} (${modB}) in FCHAIN ${fc.id} have no IO path — missing FLOW?`,
|
|
317
|
-
fix_hint: 'Add a FLOW element with io traces between these
|
|
319
|
+
fix_hint: 'Add a FLOW element with io traces between these functions',
|
|
318
320
|
context: {
|
|
319
321
|
element_type: 'FUNC',
|
|
320
322
|
element_name: elA?.name ?? fA,
|
|
@@ -334,7 +336,7 @@ export const AO_RULES = [
|
|
|
334
336
|
{ id: 'RT-01', name: 'PhysicalBoundaryIntegrity', severity: 'error', evaluate: rt01PhysicalBoundaryIntegrity },
|
|
335
337
|
{ id: 'PH-01', name: 'PhysicalModCompleteness', severity: 'info', evaluate: ph01PhysicalModCompleteness },
|
|
336
338
|
{ id: 'CA-01', name: 'CapabilityAllocation', severity: 'error', evaluate: ca01CapabilityAllocation },
|
|
337
|
-
{ id: 'IO-01', name: '
|
|
339
|
+
{ id: 'IO-01', name: 'FuncPairIOCompleteness', severity: 'warning', evaluate: io01CrossModuleCompleteness },
|
|
338
340
|
];
|
|
339
341
|
export function evaluateAORules(graph) {
|
|
340
342
|
return AO_RULES.flatMap(r => r.evaluate(graph));
|
package/dist/se/evaluate-all.js
CHANGED
|
@@ -9,6 +9,7 @@ import { CR_RULES, evaluateCRRules } from './cr-quality-rules.js';
|
|
|
9
9
|
import { ND_RULES, evaluateNDRules } from './near-duplicate-rules.js';
|
|
10
10
|
import { AO_RULES, evaluateAORules } from './ao-rules.js';
|
|
11
11
|
import { BQ_RULES, evaluateBQRules } from './quality-rules.js';
|
|
12
|
+
import { AF_RULES, evaluateAFRules } from './analysis-freshness-rules.js';
|
|
12
13
|
/** All prescribed rule definitions (single source of truth for the catalog). */
|
|
13
14
|
export const ALL_RULE_DEFS = [
|
|
14
15
|
...V3_RULES.map(r => ({ id: r.id, name: r.name, severity: r.severity })),
|
|
@@ -22,10 +23,11 @@ export const ALL_RULE_DEFS = [
|
|
|
22
23
|
...AO_RULES.map(r => ({ id: r.id, name: r.name, severity: r.severity })),
|
|
23
24
|
...FM_RULES.map(r => ({ id: r.id, name: r.name, severity: r.severity })),
|
|
24
25
|
...VIEW_RULES.map(r => ({ id: r.id, name: r.name, severity: r.severity })),
|
|
26
|
+
...AF_RULES.map(r => ({ id: r.id, name: r.name, severity: r.severity })),
|
|
25
27
|
];
|
|
26
28
|
// CR-SM-221: 'RD-' was missing — 'RD-01'.startsWith('R-') is false, so the
|
|
27
29
|
// decomposition rules ran in `default` only and never in the `se` profile.
|
|
28
|
-
const SE_PREFIXES = ['R-', 'RD-', 'UC-', 'FC-', 'SC-', 'MT-', 'CR-', 'AO-', 'FM-', 'NFR-', 'RT-', 'PH-', 'CA-', 'IO-', 'VR-', 'CL-'];
|
|
30
|
+
const SE_PREFIXES = ['R-', 'RD-', 'UC-', 'FC-', 'SC-', 'MT-', 'CR-', 'AO-', 'FM-', 'NFR-', 'RT-', 'PH-', 'CA-', 'IO-', 'VR-', 'CL-', 'AF-'];
|
|
29
31
|
const CODING_PREFIXES = ['BQ-', 'ND-'];
|
|
30
32
|
export function getRuleDefsForProfile(profile) {
|
|
31
33
|
if (profile === 'se')
|
|
@@ -48,5 +50,6 @@ export function evaluateAllRules(graph) {
|
|
|
48
50
|
...evaluateAORules(graph),
|
|
49
51
|
...evaluateFMRules(graph),
|
|
50
52
|
...evaluateViewRules(graph),
|
|
53
|
+
...evaluateAFRules(graph),
|
|
51
54
|
];
|
|
52
55
|
}
|
|
@@ -6,5 +6,6 @@ import type { RuleDefinition, RuleViolation } from './rules.js';
|
|
|
6
6
|
export declare function fc01ActorBoundary(graph: OntologyGraph): RuleViolation[];
|
|
7
7
|
export declare function fc02LeafUcHasFchain(graph: OntologyGraph): RuleViolation[];
|
|
8
8
|
export declare function fc03FchainFlat(graph: OntologyGraph): RuleViolation[];
|
|
9
|
+
export declare function fc04ActorBounded(graph: OntologyGraph): RuleViolation[];
|
|
9
10
|
export declare const FC_RULES: RuleDefinition[];
|
|
10
11
|
export declare function evaluateFCRules(graph: OntologyGraph): RuleViolation[];
|
|
@@ -93,12 +93,55 @@ export function fc03FchainFlat(graph) {
|
|
|
93
93
|
return violations;
|
|
94
94
|
}
|
|
95
95
|
// ---------------------------------------------------------------------------
|
|
96
|
+
// FC-04: FCHAIN is actor-bounded — trigger AND consumer (CR-SM-226).
|
|
97
|
+
// Distinct from FC-01: FC-01 is satisfied by ANY single actor connection (one
|
|
98
|
+
// direction only), and even allows a UC-level bypass (the parent UC's own
|
|
99
|
+
// ACTOR io counts, with no FUNC-level connection at all). FC-04 is stricter —
|
|
100
|
+
// it requires BOTH an entry (ACTOR→FLOW→FUNC∈chain, something triggers the
|
|
101
|
+
// chain) AND an exit (FUNC∈chain→FLOW→ACTOR, the chain produces something
|
|
102
|
+
// back to an actor), evaluated purely at the FUNC/FLOW level. This catches
|
|
103
|
+
// the "hollow chain" FC-01 cannot see: a chain with an entry but no exit (or
|
|
104
|
+
// vice versa), or one that only looks bounded because its parent UC happens
|
|
105
|
+
// to have unrelated ACTOR io.
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
export function fc04ActorBounded(graph) {
|
|
108
|
+
return graph.elements
|
|
109
|
+
.filter(e => e.type === 'FCHAIN')
|
|
110
|
+
.filter(fc => {
|
|
111
|
+
const funcIds = new Set(graph.traces
|
|
112
|
+
.filter(t => t.source === fc.id && t.type === 'compose')
|
|
113
|
+
.map(t => t.target)
|
|
114
|
+
.filter(id => graph.elements.some(e => e.id === id && e.type === 'FUNC')));
|
|
115
|
+
if (funcIds.size === 0)
|
|
116
|
+
return false; // R-15 already flags the empty-chain case
|
|
117
|
+
const io = graph.traces.filter(t => t.type === 'io');
|
|
118
|
+
const isActor = (id) => graph.elements.some(e => e.id === id && e.type === 'ACTOR');
|
|
119
|
+
const isFlow = (id) => graph.elements.some(e => e.id === id && e.type === 'FLOW');
|
|
120
|
+
// Entry: ACTOR→FLOW→FUNC∈chain (something triggers the chain).
|
|
121
|
+
const entry = io.some(e => funcIds.has(e.target) && isFlow(e.source) &&
|
|
122
|
+
io.some(a => a.target === e.source && isActor(a.source)));
|
|
123
|
+
// Exit: FUNC∈chain→FLOW→ACTOR (the chain produces something back to an actor).
|
|
124
|
+
const exit = io.some(e => funcIds.has(e.source) && isFlow(e.target) &&
|
|
125
|
+
io.some(a => a.source === e.target && isActor(a.target)));
|
|
126
|
+
return !(entry && exit);
|
|
127
|
+
})
|
|
128
|
+
.map(fc => ({
|
|
129
|
+
rule_id: 'FC-04',
|
|
130
|
+
severity: 'warning',
|
|
131
|
+
element_id: fc.id,
|
|
132
|
+
message: `${fc.id} is not actor-bounded (needs an ACTOR trigger into AND an ACTOR consumer out of the chain)`,
|
|
133
|
+
fix_hint: 'Link an ACTOR→FLOW→FUNC entry and a FUNC→FLOW→ACTOR exit for at least one FUNC in the chain',
|
|
134
|
+
context: { element_type: fc.type, element_name: fc.name },
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
96
138
|
// Aggregated
|
|
97
139
|
// ---------------------------------------------------------------------------
|
|
98
140
|
export const FC_RULES = [
|
|
99
141
|
{ id: 'FC-01', name: 'FCHAIN has actor boundary', severity: 'warning', evaluate: fc01ActorBoundary },
|
|
100
142
|
{ id: 'FC-02', name: 'Leaf UC has FCHAIN', severity: 'warning', evaluate: fc02LeafUcHasFchain },
|
|
101
143
|
{ id: 'FC-03', name: 'FCHAIN is flat', severity: 'warning', evaluate: fc03FchainFlat },
|
|
144
|
+
{ id: 'FC-04', name: 'FCHAIN actor-bounded (trigger+consumer)', severity: 'warning', evaluate: fc04ActorBounded },
|
|
102
145
|
];
|
|
103
146
|
export function evaluateFCRules(graph) {
|
|
104
147
|
return FC_RULES.flatMap(rule => rule.evaluate(graph));
|
package/dist/se/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/** Ontology schema version (element types + trace types). */
|
|
6
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.23.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';
|
|
@@ -21,6 +21,7 @@ export * from './cr-quality-rules.js';
|
|
|
21
21
|
export * from './near-duplicate-rules.js';
|
|
22
22
|
export * from './ao-rules.js';
|
|
23
23
|
export * from './quality-rules.js';
|
|
24
|
+
export * from './analysis-freshness-rules.js';
|
|
24
25
|
export * from './evaluate-all.js';
|
|
25
26
|
export * from './readiness.js';
|
|
26
27
|
export * from './meta-model.js';
|
package/dist/se/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/** Ontology schema version (element types + trace types). */
|
|
6
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.23.0'; // CR-SM-227: +AF-01..05 Analysis-Freshness-Legs (analysis-freshness-rules.ts) — the third leg-kind a review gate checks (rule legs, layer-presence legs [R-28], now analysis-freshness legs, docs/articles/07-the-scoring-landscape.md); presence-only rules (Presence/Resolution-Split like R-19/R-20/R-26/R-27 vs. RC-01..05) checking a `graphVersion` freshness stamp exists under SYS.attributes.analysisFreshness.<AnalysisArtifactId> for each of the 5 CR-GC-221 judgment artifacts (conops/trade/assumption-review/fmea/implplan) — staleness (stamp vs live graphVersion) stays a consumer/I-O concern (`CreationCurrencyProvider`, @sigloch/graphcode-client), NOT evaluated here; Familie-Review 2026-08-05 gate assignment → RULE_TO_PHASE: ConOps/Trade/Assumption-Review → PDR, FMEA → CDR, Implementation Plan → TRR (SRR gets none); +AnalysisArtifactId enum + AnalysisFreshnessStampSchema (ontology.ts) as the SSOT graphcode-client's ARTIFACT_CATALOG analysis ids must match; grober Graph-Versions-Stempel only, no scope-hash in this first cut (conscious CR-SM-227 limitation).
|
|
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';
|
|
@@ -21,6 +21,7 @@ export * from './cr-quality-rules.js';
|
|
|
21
21
|
export * from './near-duplicate-rules.js';
|
|
22
22
|
export * from './ao-rules.js';
|
|
23
23
|
export * from './quality-rules.js';
|
|
24
|
+
export * from './analysis-freshness-rules.js';
|
|
24
25
|
export * from './evaluate-all.js';
|
|
25
26
|
export * from './readiness.js';
|
|
26
27
|
export * from './meta-model.js';
|
package/dist/se/ontology.d.ts
CHANGED
|
@@ -131,6 +131,34 @@ export declare const RealRefSchema: z.ZodObject<{
|
|
|
131
131
|
lang: z.ZodOptional<z.ZodString>;
|
|
132
132
|
}, z.core.$strip>;
|
|
133
133
|
export type RealRef = z.infer<typeof RealRefSchema>;
|
|
134
|
+
/**
|
|
135
|
+
* The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
|
|
136
|
+
* skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
|
|
137
|
+
* SSOT — `@sigloch/graphcode-client`'s `ARTIFACT_CATALOG` (`kind:'analysis'` rows)
|
|
138
|
+
* must use exactly these ids.
|
|
139
|
+
*/
|
|
140
|
+
export declare const AnalysisArtifactId: z.ZodEnum<{
|
|
141
|
+
conops: "conops";
|
|
142
|
+
fmea: "fmea";
|
|
143
|
+
trade: "trade";
|
|
144
|
+
implplan: "implplan";
|
|
145
|
+
"assumption-review": "assumption-review";
|
|
146
|
+
}>;
|
|
147
|
+
export type AnalysisArtifactId = z.infer<typeof AnalysisArtifactId>;
|
|
148
|
+
/**
|
|
149
|
+
* A "this analysis covers graph-stand V" stamp (CR-SM-227) — the grober
|
|
150
|
+
* Graph-Versions-Stempel Familie-Review decided (no scope-hash in the first
|
|
151
|
+
* cut, see CR-SM-227 "Achtung"). `graphVersion` is the harness's monotone
|
|
152
|
+
* counter (`ToolContext.graphVersion()`, graphcode) at the moment the
|
|
153
|
+
* artifact was written — I/O, so writing it stays a consumer (graphcode)
|
|
154
|
+
* concern; contracts only defines the shape.
|
|
155
|
+
* Stored under `OntologyElement.attributes.analysisFreshness.<AnalysisArtifactId>`
|
|
156
|
+
* on the SYS root element (same anchor convention as R-28).
|
|
157
|
+
*/
|
|
158
|
+
export declare const AnalysisFreshnessStampSchema: z.ZodObject<{
|
|
159
|
+
graphVersion: z.ZodNumber;
|
|
160
|
+
}, z.core.$strip>;
|
|
161
|
+
export type AnalysisFreshnessStamp = z.infer<typeof AnalysisFreshnessStampSchema>;
|
|
134
162
|
/**
|
|
135
163
|
* An element (node) in the SE ontology graph.
|
|
136
164
|
* `attributes` holds type-specific properties (e.g. FUNC.safety_relevant).
|
package/dist/se/ontology.js
CHANGED
|
@@ -92,6 +92,26 @@ export const RealRefSchema = z.object({
|
|
|
92
92
|
symbol: z.string().optional(),
|
|
93
93
|
lang: z.string().optional(),
|
|
94
94
|
});
|
|
95
|
+
/**
|
|
96
|
+
* The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
|
|
97
|
+
* skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
|
|
98
|
+
* SSOT — `@sigloch/graphcode-client`'s `ARTIFACT_CATALOG` (`kind:'analysis'` rows)
|
|
99
|
+
* must use exactly these ids.
|
|
100
|
+
*/
|
|
101
|
+
export const AnalysisArtifactId = z.enum(['conops', 'fmea', 'trade', 'implplan', 'assumption-review']);
|
|
102
|
+
/**
|
|
103
|
+
* A "this analysis covers graph-stand V" stamp (CR-SM-227) — the grober
|
|
104
|
+
* Graph-Versions-Stempel Familie-Review decided (no scope-hash in the first
|
|
105
|
+
* cut, see CR-SM-227 "Achtung"). `graphVersion` is the harness's monotone
|
|
106
|
+
* counter (`ToolContext.graphVersion()`, graphcode) at the moment the
|
|
107
|
+
* artifact was written — I/O, so writing it stays a consumer (graphcode)
|
|
108
|
+
* concern; contracts only defines the shape.
|
|
109
|
+
* Stored under `OntologyElement.attributes.analysisFreshness.<AnalysisArtifactId>`
|
|
110
|
+
* on the SYS root element (same anchor convention as R-28).
|
|
111
|
+
*/
|
|
112
|
+
export const AnalysisFreshnessStampSchema = z.object({
|
|
113
|
+
graphVersion: z.number().int().nonnegative(),
|
|
114
|
+
});
|
|
95
115
|
/**
|
|
96
116
|
* An element (node) in the SE ontology graph.
|
|
97
117
|
* `attributes` holds type-specific properties (e.g. FUNC.safety_relevant).
|
package/dist/se/readiness.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CR-120: Readiness dimension schemas for dynamic phase readiness.
|
|
3
3
|
* Dimensions emerge from graph state — no state machine.
|
|
4
|
+
* CR-SM-226: `emergentPhase`/`phaseScore` (CR-120/146) removed — a third,
|
|
5
|
+
* undocumented phase construct next to RULE_TO_DIMENSION and the (then still
|
|
6
|
+
* ad-hoc) phase-gate grouping. `RULE_TO_PHASE` below is the one replacement:
|
|
7
|
+
* a rule → SRR/PDR/CDR/TRR mapping, same shape and same completeness
|
|
8
|
+
* guarantee as `RULE_TO_DIMENSION`, exported so no consumer reinvents it.
|
|
4
9
|
*/
|
|
5
10
|
import { z } from 'zod/v4';
|
|
6
11
|
export declare const ReadinessDimension: z.ZodEnum<{
|
|
@@ -48,8 +53,6 @@ export declare const ReadinessReport: z.ZodObject<{
|
|
|
48
53
|
applicable: z.ZodNumber;
|
|
49
54
|
ready: z.ZodBoolean;
|
|
50
55
|
}, z.core.$strip>>;
|
|
51
|
-
emergentPhase: z.ZodString;
|
|
52
|
-
phaseScore: z.ZodNumber;
|
|
53
56
|
overallScore: z.ZodNumber;
|
|
54
57
|
timestamp: z.ZodISODateTime;
|
|
55
58
|
}, z.core.$strip>;
|
|
@@ -60,3 +63,24 @@ export type ReadinessReportType = z.infer<typeof ReadinessReport>;
|
|
|
60
63
|
* since its core concern is test verification coverage.
|
|
61
64
|
*/
|
|
62
65
|
export declare const RULE_TO_DIMENSION: Record<string, ReadinessDimensionType>;
|
|
66
|
+
/** INCOSE technical-review gates, in lifecycle order. */
|
|
67
|
+
export declare const PhaseGate: z.ZodEnum<{
|
|
68
|
+
SRR: "SRR";
|
|
69
|
+
PDR: "PDR";
|
|
70
|
+
CDR: "CDR";
|
|
71
|
+
TRR: "TRR";
|
|
72
|
+
}>;
|
|
73
|
+
export type PhaseGateType = z.infer<typeof PhaseGate>;
|
|
74
|
+
/**
|
|
75
|
+
* Rule → phase-gate mapping (CR-SM-226). SRR = requirements/scope clarity,
|
|
76
|
+
* PDR = architecture/functional completeness, CDR = critical design/schema
|
|
77
|
+
* completeness, TRR = test readiness. Primary-gate assignment, same
|
|
78
|
+
* single-owner convention as RULE_TO_DIMENSION.
|
|
79
|
+
*/
|
|
80
|
+
export declare const RULE_TO_PHASE: Record<string, PhaseGateType>;
|
|
81
|
+
/** The 8 RULE_TO_DIMENSION topic scores (req/uc/arch/alloc/ver/schema/cr/ms). */
|
|
82
|
+
export declare const DIMENSION_READINESS_NAME = "dimension_readiness";
|
|
83
|
+
/** The 4 RULE_TO_PHASE gate scores (SRR/PDR/CDR/TRR). */
|
|
84
|
+
export declare const PHASE_READINESS_NAME = "phase_readiness";
|
|
85
|
+
/** Best-of-N ranking KPI: candidate dimension_readiness minus baseline. */
|
|
86
|
+
export declare const DIMENSION_READINESS_DELTA_NAME = "dimension_readiness_delta";
|
package/dist/se/readiness.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CR-120: Readiness dimension schemas for dynamic phase readiness.
|
|
3
3
|
* Dimensions emerge from graph state — no state machine.
|
|
4
|
+
* CR-SM-226: `emergentPhase`/`phaseScore` (CR-120/146) removed — a third,
|
|
5
|
+
* undocumented phase construct next to RULE_TO_DIMENSION and the (then still
|
|
6
|
+
* ad-hoc) phase-gate grouping. `RULE_TO_PHASE` below is the one replacement:
|
|
7
|
+
* a rule → SRR/PDR/CDR/TRR mapping, same shape and same completeness
|
|
8
|
+
* guarantee as `RULE_TO_DIMENSION`, exported so no consumer reinvents it.
|
|
4
9
|
*/
|
|
5
10
|
import { z } from 'zod/v4';
|
|
6
11
|
export const ReadinessDimension = z.enum([
|
|
7
12
|
'req', // Requirements quality (BQ-01..07, RD-01..03)
|
|
8
|
-
'uc', // UC completeness (UC-01..06, R-14, FC-01..
|
|
13
|
+
'uc', // UC completeness (UC-01..06, R-14, FC-01..04)
|
|
9
14
|
'arch', // Functional architecture (R-02, R-03, R-10, R-12)
|
|
10
15
|
'alloc', // Module allocation (R-04)
|
|
11
16
|
'ver', // Test coverage (R-01, R-05)
|
|
12
|
-
'schema', // Interface completeness (R-26 binding, SC-02 usage)
|
|
17
|
+
'schema', // Interface completeness (R-26 binding, SC-02/SC-04 usage)
|
|
13
18
|
'cr', // CR traceability (CR-R01..R03)
|
|
14
19
|
'ms', // Milestone planning (MS-01..02)
|
|
15
20
|
]);
|
|
@@ -22,8 +27,6 @@ export const ReadinessScore = z.object({
|
|
|
22
27
|
});
|
|
23
28
|
export const ReadinessReport = z.object({
|
|
24
29
|
scores: z.array(ReadinessScore),
|
|
25
|
-
emergentPhase: z.string(),
|
|
26
|
-
phaseScore: z.number().min(0).max(1), // CR-146: % progress of current phase
|
|
27
30
|
overallScore: z.number().min(0).max(1),
|
|
28
31
|
timestamp: z.iso.datetime(),
|
|
29
32
|
});
|
|
@@ -42,11 +45,14 @@ export const RULE_TO_DIMENSION = {
|
|
|
42
45
|
// trace/realization/allocation completeness rules (CR-228 D: previously unmapped → advisory fall-through)
|
|
43
46
|
'R-18': 'arch', 'R-19': 'ver', 'R-20': 'arch', 'R-21': 'ver',
|
|
44
47
|
'R-22': 'alloc', 'R-23': 'alloc', 'R-26': 'schema', 'R-27': 'arch',
|
|
48
|
+
// CR-SM-226: R-28 Ebenen-Präsenz (>1 FUNC needs FLOW+SCHEMA) is an architecture rule.
|
|
49
|
+
'R-28': 'arch',
|
|
45
50
|
// uc
|
|
46
51
|
'UC-01': 'uc', 'UC-02': 'uc', 'UC-03': 'uc', 'UC-04': 'uc',
|
|
47
52
|
'UC-05': 'uc', 'UC-06': 'uc',
|
|
48
53
|
'R-14': 'uc', 'R-15': 'uc', 'R-16': 'uc', 'R-17': 'uc',
|
|
49
|
-
|
|
54
|
+
// FC-04 (CR-SM-226): FCHAIN actor-bounded (trigger+consumer) — same dimension as FC-01..03.
|
|
55
|
+
'FC-01': 'uc', 'FC-02': 'uc', 'FC-03': 'uc', 'FC-04': 'uc',
|
|
50
56
|
// arch
|
|
51
57
|
'R-02': 'arch', 'R-03': 'arch', 'R-10': 'arch', 'R-12': 'arch',
|
|
52
58
|
// alloc
|
|
@@ -55,6 +61,8 @@ export const RULE_TO_DIMENSION = {
|
|
|
55
61
|
'R-01': 'ver', 'R-05': 'ver',
|
|
56
62
|
// schema
|
|
57
63
|
'SC-02': 'schema', // SC-01/SC-03 deleted (BOK-CR-026) — R-26 is the binding rule
|
|
64
|
+
// CR-SM-226: SC-04 sharp per-FLOW SCHEMA-binding check — same dimension as SC-02.
|
|
65
|
+
'SC-04': 'schema',
|
|
58
66
|
// structural rules without primary dimension → assigned by closest concern
|
|
59
67
|
'R-08': 'arch',
|
|
60
68
|
// near-duplicate detection
|
|
@@ -73,6 +81,11 @@ export const RULE_TO_DIMENSION = {
|
|
|
73
81
|
'MS-01': 'ms', 'MS-02': 'ms', 'MS-03': 'ms',
|
|
74
82
|
// FMEA / risk
|
|
75
83
|
'FM-01': 'req', 'FM-02': 'req', 'FM-03': 'ver',
|
|
84
|
+
// CR-SM-227: analysis-artifact freshness presence — closest topical fit per artifact.
|
|
85
|
+
'AF-01': 'req', 'AF-03': 'req', // ConOps/Assumption-Review: requirements-adjacent judgment work
|
|
86
|
+
'AF-02': 'arch', // Trade Study: architecture decision record
|
|
87
|
+
'AF-04': 'ver', // FMEA: risk/verification, same bucket as FM-03
|
|
88
|
+
'AF-05': 'ms', // Implementation Plan: milestone/planning
|
|
76
89
|
// NFR budget
|
|
77
90
|
'NFR-01': 'arch',
|
|
78
91
|
// cross-module IO (CR-192)
|
|
@@ -81,3 +94,71 @@ export const RULE_TO_DIMENSION = {
|
|
|
81
94
|
'VR-01': 'ver',
|
|
82
95
|
'CL-01': 'uc',
|
|
83
96
|
};
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Phase-gate readiness (CR-SM-226) — the INCOSE technical-review axis.
|
|
99
|
+
// Orthogonal to RULE_TO_DIMENSION (8 topic scores): RULE_TO_PHASE groups the
|
|
100
|
+
// SAME rule violations into the 4 lifecycle gates SRR/PDR/CDR/TRR. Both are
|
|
101
|
+
// projections of one rule stream — "three views over the same rule
|
|
102
|
+
// violations" (docs/articles/07-the-scoring-landscape.md), not three
|
|
103
|
+
// independent scoring systems. No advisory fall-through: every rule in
|
|
104
|
+
// ALL_RULE_DEFS must appear here (enforced by a completeness test analogous
|
|
105
|
+
// to the RULE_TO_DIMENSION one).
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
/** INCOSE technical-review gates, in lifecycle order. */
|
|
108
|
+
export const PhaseGate = z.enum(['SRR', 'PDR', 'CDR', 'TRR']);
|
|
109
|
+
/**
|
|
110
|
+
* Rule → phase-gate mapping (CR-SM-226). SRR = requirements/scope clarity,
|
|
111
|
+
* PDR = architecture/functional completeness, CDR = critical design/schema
|
|
112
|
+
* completeness, TRR = test readiness. Primary-gate assignment, same
|
|
113
|
+
* single-owner convention as RULE_TO_DIMENSION.
|
|
114
|
+
*/
|
|
115
|
+
export const RULE_TO_PHASE = {
|
|
116
|
+
// CR-SM-227: Analysis-Freshness-Legs (AF-01..05) — Familie-Review 2026-08-05
|
|
117
|
+
// gate assignment, final: ConOps/Trade/Assumption-Review → PDR, FMEA → CDR,
|
|
118
|
+
// Implementation Plan → TRR. SRR gets no analysis-freshness leg.
|
|
119
|
+
'AF-01': 'PDR', 'AF-02': 'PDR', 'AF-03': 'PDR', // ConOps, Trade Study, Assumption Review
|
|
120
|
+
'AF-04': 'CDR', // FMEA
|
|
121
|
+
'AF-05': 'TRR', // Implementation Plan
|
|
122
|
+
// SRR — requirements/scope clarity, system+UC boundary definition.
|
|
123
|
+
'BQ-01': 'SRR', 'BQ-02': 'SRR', 'BQ-04': 'SRR', 'BQ-06': 'SRR', 'BQ-07': 'SRR',
|
|
124
|
+
'RD-01': 'SRR', 'RD-02': 'SRR', 'RD-03': 'SRR',
|
|
125
|
+
'UC-01': 'SRR', 'UC-02': 'SRR', 'UC-03': 'SRR', 'UC-04': 'SRR', 'UC-05': 'SRR', 'UC-06': 'SRR',
|
|
126
|
+
'R-14': 'SRR', 'R-16': 'SRR', 'R-17': 'SRR',
|
|
127
|
+
'FC-02': 'SRR',
|
|
128
|
+
'CL-01': 'SRR',
|
|
129
|
+
'FM-01': 'SRR', 'FM-02': 'SRR',
|
|
130
|
+
'CR-R01': 'SRR', 'CR-R03': 'SRR',
|
|
131
|
+
'MS-01': 'SRR', 'MS-02': 'SRR', 'MS-03': 'SRR',
|
|
132
|
+
// PDR — architecture/functional completeness.
|
|
133
|
+
'RD-04': 'PDR',
|
|
134
|
+
'R-02': 'PDR', 'R-03': 'PDR', 'R-08': 'PDR', 'R-10': 'PDR', 'R-12': 'PDR', 'R-18': 'PDR',
|
|
135
|
+
'R-04': 'PDR', 'R-22': 'PDR', 'R-23': 'PDR',
|
|
136
|
+
'R-15': 'PDR',
|
|
137
|
+
'FC-01': 'PDR', 'FC-03': 'PDR', 'FC-04': 'PDR',
|
|
138
|
+
'R-28': 'PDR', // Ebenen-Präsenz (CR-SM-226)
|
|
139
|
+
'MT-01': 'PDR', 'MT-02': 'PDR',
|
|
140
|
+
'ND-01': 'PDR',
|
|
141
|
+
'AO-D01': 'PDR', 'AO-D03': 'PDR', 'CR-01': 'PDR', 'RT-01': 'PDR', 'PH-01': 'PDR', 'CA-01': 'PDR',
|
|
142
|
+
'IO-01': 'PDR', // CR-SM-226: extended to all FCHAIN FUNC-pairs, added to the phase axis.
|
|
143
|
+
'CR-R04': 'PDR',
|
|
144
|
+
// CDR — critical design/schema completeness.
|
|
145
|
+
'R-26': 'CDR', 'R-27': 'CDR',
|
|
146
|
+
'SC-02': 'CDR', 'SC-04': 'CDR', // SC-04: FLOW→SCHEMA sharp rule (CR-SM-226)
|
|
147
|
+
'ND-02': 'CDR',
|
|
148
|
+
'NFR-01': 'CDR',
|
|
149
|
+
// TRR — test readiness.
|
|
150
|
+
'R-01': 'TRR', 'R-05': 'TRR', 'R-19': 'TRR', 'R-20': 'TRR', 'R-21': 'TRR',
|
|
151
|
+
'VR-01': 'TRR',
|
|
152
|
+
'FM-03': 'TRR',
|
|
153
|
+
'CR-R02': 'TRR',
|
|
154
|
+
};
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Sprachregelung (CR-SM-226) — naming SSOT so graphcode/graph-view-edit/
|
|
157
|
+
// article render the same terms instead of re-inventing string literals.
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
/** The 8 RULE_TO_DIMENSION topic scores (req/uc/arch/alloc/ver/schema/cr/ms). */
|
|
160
|
+
export const DIMENSION_READINESS_NAME = 'dimension_readiness';
|
|
161
|
+
/** The 4 RULE_TO_PHASE gate scores (SRR/PDR/CDR/TRR). */
|
|
162
|
+
export const PHASE_READINESS_NAME = 'phase_readiness';
|
|
163
|
+
/** Best-of-N ranking KPI: candidate dimension_readiness minus baseline. */
|
|
164
|
+
export const DIMENSION_READINESS_DELTA_NAME = 'dimension_readiness_delta';
|
package/dist/se/rules.js
CHANGED
|
@@ -898,6 +898,41 @@ function physicalModMustHaveRealRef(graph) {
|
|
|
898
898
|
context: { element_type: mod.type, element_name: mod.name },
|
|
899
899
|
}));
|
|
900
900
|
}
|
|
901
|
+
// ---------------------------------------------------------------------------
|
|
902
|
+
// R-28: Ebenen-Präsenz — architecture-level presence (CR-SM-226).
|
|
903
|
+
// Closes the vacuous-complete hole: with 0 or 1 FUNC, per-element rules like
|
|
904
|
+
// R-15/FC-04/SC-04 fire 0 times and every completeness leg reads "complete" —
|
|
905
|
+
// not because the architecture is done, but because there is nothing to check
|
|
906
|
+
// yet. Once >1 FUNC exists, the graph must ALSO carry at least one FLOW (data
|
|
907
|
+
// moves between functions) AND at least one SCHEMA (that data has a
|
|
908
|
+
// contract) — ONE combined rule, not staged PDR/CDR checks (the CR draft's
|
|
909
|
+
// two-stage proposal was simplified in Familie-Review 2026-08-04).
|
|
910
|
+
// ---------------------------------------------------------------------------
|
|
911
|
+
function ebenenPraesenz(graph) {
|
|
912
|
+
const funcs = graph.elements.filter(e => e.type === 'FUNC');
|
|
913
|
+
if (funcs.length <= 1)
|
|
914
|
+
return [];
|
|
915
|
+
const flows = graph.elements.filter(e => e.type === 'FLOW');
|
|
916
|
+
const schemas = graph.elements.filter(e => e.type === 'SCHEMA');
|
|
917
|
+
const missingLevels = [];
|
|
918
|
+
if (flows.length === 0)
|
|
919
|
+
missingLevels.push('FLOW');
|
|
920
|
+
if (schemas.length === 0)
|
|
921
|
+
missingLevels.push('SCHEMA');
|
|
922
|
+
if (missingLevels.length === 0)
|
|
923
|
+
return [];
|
|
924
|
+
// Anchor the graph-wide violation on SYS (the root) if present, else the
|
|
925
|
+
// first FUNC — there is no single "population" element this rule checks.
|
|
926
|
+
const anchor = graph.elements.find(e => e.type === 'SYS') ?? funcs[0];
|
|
927
|
+
return [{
|
|
928
|
+
rule_id: 'R-28',
|
|
929
|
+
severity: 'warning',
|
|
930
|
+
element_id: anchor.id,
|
|
931
|
+
message: `Graph has ${funcs.length} FUNCs but no ${missingLevels.join(' and no ')} element — architecture level(s) absent`,
|
|
932
|
+
fix_hint: `Add at least one ${missingLevels.join(' and one ')} element to bind the functional and data-level architecture`,
|
|
933
|
+
context: { element_type: anchor.type, element_name: anchor.name },
|
|
934
|
+
}];
|
|
935
|
+
}
|
|
901
936
|
export const V3_RULES = [
|
|
902
937
|
{ id: 'R-01', name: 'REQ must have verification', severity: 'error', evaluate: reqMustHaveVerification },
|
|
903
938
|
{ id: 'R-02', name: 'FUNC must satisfy REQ', severity: 'warning', evaluate: funcMustSatisfyReq },
|
|
@@ -925,6 +960,7 @@ export const V3_RULES = [
|
|
|
925
960
|
{ id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', evaluate: decompositionBreadth },
|
|
926
961
|
{ id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', evaluate: msEmptyScope },
|
|
927
962
|
{ id: 'MS-02', name: 'Milestone dangling dependency', severity: 'error', evaluate: msDanglingDependency },
|
|
963
|
+
{ id: 'R-28', name: 'Ebenen-Präsenz (FLOW+SCHEMA when funcCount>1)', severity: 'warning', evaluate: ebenenPraesenz },
|
|
928
964
|
];
|
|
929
965
|
/** Run all rules against a graph */
|
|
930
966
|
export function evaluateRules(graph) {
|
|
@@ -15,5 +15,6 @@
|
|
|
15
15
|
import type { OntologyGraph } from './ontology.js';
|
|
16
16
|
import type { RuleDefinition, RuleViolation } from './rules.js';
|
|
17
17
|
export declare function sc02IsReferenced(graph: OntologyGraph): RuleViolation[];
|
|
18
|
+
export declare function sc04FlowHasSchema(graph: OntologyGraph): RuleViolation[];
|
|
18
19
|
export declare const SC_RULES: RuleDefinition[];
|
|
19
20
|
export declare function evaluateSCRules(graph: OntologyGraph): RuleViolation[];
|
|
@@ -20,10 +20,37 @@ 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.
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
23
49
|
// Aggregated array & convenience runner
|
|
24
50
|
// ---------------------------------------------------------------------------
|
|
25
51
|
export const SC_RULES = [
|
|
26
52
|
{ id: 'SC-02', name: 'Schema referenced by FLOW', severity: 'warning', evaluate: sc02IsReferenced },
|
|
53
|
+
{ id: 'SC-04', name: 'FLOW has SCHEMA binding', severity: 'warning', evaluate: sc04FlowHasSchema },
|
|
27
54
|
];
|
|
28
55
|
export function evaluateSCRules(graph) {
|
|
29
56
|
return SC_RULES.flatMap(rule => rule.evaluate(graph));
|