@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
package/dist/se/ontology.js
CHANGED
|
@@ -72,36 +72,24 @@ export const TestRefSchema = z.object({
|
|
|
72
72
|
level: z.string().optional(),
|
|
73
73
|
});
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* - `
|
|
82
|
-
* - `
|
|
83
|
-
*
|
|
84
|
-
*
|
|
75
|
+
* Realization binding for an element (CR-228, ontology bump 3.9.0) — unifies the
|
|
76
|
+
* byte-identical former `codeRef` (FUNC → code symbol) and `schemaRef` (SCHEMA →
|
|
77
|
+
* Zod export), and extends to MOD (physical part → CAD/geometry artefact). The
|
|
78
|
+
* *type* of the pointing element disambiguates what kind of realization it is:
|
|
79
|
+
* FUNC→code, SCHEMA→Zod-def, physical MOD→CAD. TEST keeps its own `testRef`
|
|
80
|
+
* (a TEST is not just located but *executed* — the runner is the extra value).
|
|
81
|
+
* - `file` : realization file path, e.g. `src/harness.ts`, `part.step`.
|
|
82
|
+
* - `symbol` : the realizing symbol (function/class/Zod export). Optional — a
|
|
83
|
+
* geometry artefact (CAD) has no symbol; file-exists is the binding.
|
|
84
|
+
* - `lang` : optional language/format id (default `ts`) — selects the
|
|
85
|
+
* conformance engine, so the binding is language-agnostic.
|
|
86
|
+
* Stored under `OntologyElement.attributes.realRef` (additive, opt-in validation).
|
|
87
|
+
* The free-text SCHEMA `contract` attribute stays a human description, not the
|
|
88
|
+
* conformance basis: RC-01/RC-03 resolve this binding, RC-04 checks it is parsed.
|
|
85
89
|
*/
|
|
86
|
-
export const
|
|
90
|
+
export const RealRefSchema = z.object({
|
|
87
91
|
file: RepoRelativePathSchema,
|
|
88
|
-
symbol: z.string(),
|
|
89
|
-
lang: z.string().optional(),
|
|
90
|
-
});
|
|
91
|
-
/**
|
|
92
|
-
* Binding of a SCHEMA node to the Zod schema that defines it (CR-211), analogous
|
|
93
|
-
* to CodeRefSchema for FUNC:
|
|
94
|
-
* - `file` : the source file declaring the Zod schema, e.g. `src/se/ontology.ts`.
|
|
95
|
-
* - `symbol` : the exported schema symbol, e.g. `CodeRefSchema`.
|
|
96
|
-
* - `lang` : optional language id (default `ts`).
|
|
97
|
-
* Stored under `OntologyElement.attributes.schemaRef`. The free-text `contract`
|
|
98
|
-
* attribute stays as a human description but is no longer the conformance basis:
|
|
99
|
-
* RC-03 resolves this binding to a declared export, RC-04 checks it is parsed at
|
|
100
|
-
* the modelled interface.
|
|
101
|
-
*/
|
|
102
|
-
export const SchemaRefSchema = z.object({
|
|
103
|
-
file: RepoRelativePathSchema,
|
|
104
|
-
symbol: z.string(),
|
|
92
|
+
symbol: z.string().optional(),
|
|
105
93
|
lang: z.string().optional(),
|
|
106
94
|
});
|
|
107
95
|
/**
|
|
@@ -188,8 +176,8 @@ export const ELEMENT_ATTRIBUTES = {
|
|
|
188
176
|
{ key: 'timingBudgetMs', type: 'number', description: 'NFR timing budget in ms' },
|
|
189
177
|
{ key: 'measuredMs', type: 'number', description: 'Measured execution time in ms' },
|
|
190
178
|
{ key: 'sourceFile', type: 'string', description: 'Implementation source file' },
|
|
191
|
-
{ key: '
|
|
192
|
-
{ key: 'external', type: 'boolean', description: 'Externally-realized FUNC (e.g. a renderer in another package): exempt from the R-20
|
|
179
|
+
{ key: 'realRef', type: 'object', description: 'Realization binding {file, symbol?, lang?} — the code symbol that realizes this FUNC; see RealRefSchema (CR-228, was codeRef)' },
|
|
180
|
+
{ key: 'external', type: 'boolean', description: 'Externally-realized FUNC (e.g. a renderer in another package): exempt from the R-20 realRef-binding requirement (CR-GC-205)' },
|
|
193
181
|
{ key: 'concept', type: 'boolean', description: 'Concept-only FUNC: specified, no implementation yet; exempt from R-20 (CR-GC-205)' },
|
|
194
182
|
],
|
|
195
183
|
UC: [
|
|
@@ -206,11 +194,15 @@ export const ELEMENT_ATTRIBUTES = {
|
|
|
206
194
|
],
|
|
207
195
|
MOD: [
|
|
208
196
|
{ key: 'path', type: 'string', description: 'Source file or glob the module owns, e.g. src/harness.ts — anchors the MOD<->file mapping for graph<->code conformance (CR-GC-205)' },
|
|
197
|
+
{ key: 'kind', type: 'string', description: "Module kind, e.g. 'physical' for a machine part (Bauteil) — physical MODs are realized by a CAD/geometry realRef (R-27), logical MODs by their FUNCs' code (CR-191/228)" },
|
|
198
|
+
{ key: 'realRef', type: 'object', description: 'Realization binding {file, symbol?, lang?} — CAD/geometry artefact realizing a physical MOD; see RealRefSchema (CR-228, symbol optional)' },
|
|
199
|
+
{ key: 'external', type: 'boolean', description: 'Externally-realized MOD: exempt from the R-27 realRef-presence requirement (CR-228)' },
|
|
200
|
+
{ key: 'concept', type: 'boolean', description: 'Concept-only MOD: modelled, no realization yet; exempt from R-27 (CR-228)' },
|
|
209
201
|
],
|
|
210
202
|
SCHEMA: [
|
|
211
203
|
{ key: 'contract', type: 'string', description: 'Free-text contract description, e.g. "@sigloch/contracts LiveUpdateEventSchema" — human hint only, not the conformance basis (CR-211)' },
|
|
212
|
-
{ key: '
|
|
213
|
-
{ key: 'external', type: 'boolean', description: 'Externally-defined SCHEMA (e.g. a foreign-API contract): exempt from the
|
|
214
|
-
{ key: 'concept', type: 'boolean', description: 'Concept-only SCHEMA: modelled, no Zod export yet; exempt from the
|
|
204
|
+
{ key: 'realRef', type: 'object', description: 'Realization binding {file, symbol?, lang?} — the Zod export defining this SCHEMA; see RealRefSchema (CR-228, was schemaRef)' },
|
|
205
|
+
{ key: 'external', type: 'boolean', description: 'Externally-defined SCHEMA (e.g. a foreign-API contract): exempt from the realRef-presence requirement (CR-211)' },
|
|
206
|
+
{ key: 'concept', type: 'boolean', description: 'Concept-only SCHEMA: modelled, no Zod export yet; exempt from the realRef-presence requirement (CR-211)' },
|
|
215
207
|
],
|
|
216
208
|
};
|
package/dist/se/readiness.js
CHANGED
|
@@ -9,7 +9,7 @@ export const ReadinessDimension = z.enum([
|
|
|
9
9
|
'arch', // Functional architecture (R-02, R-03, R-10, R-12)
|
|
10
10
|
'alloc', // Module allocation (R-04)
|
|
11
11
|
'ver', // Test coverage (R-01, R-05)
|
|
12
|
-
'schema', // Interface completeness (SC-
|
|
12
|
+
'schema', // Interface completeness (R-26 binding, SC-02 usage)
|
|
13
13
|
'cr', // CR traceability (CR-R01..R03)
|
|
14
14
|
'ms', // Milestone planning (MS-01..02)
|
|
15
15
|
]);
|
|
@@ -37,9 +37,11 @@ export const RULE_TO_DIMENSION = {
|
|
|
37
37
|
'BQ-01': 'req', 'BQ-02': 'req', 'BQ-04': 'req',
|
|
38
38
|
'BQ-06': 'req', 'BQ-07': 'req',
|
|
39
39
|
'RD-01': 'req', 'RD-02': 'req', 'RD-03': 'req',
|
|
40
|
+
// RD-04 is decomposition *breadth* — an architecture concern, not a requirement one
|
|
41
|
+
'RD-04': 'arch',
|
|
40
42
|
// trace/realization/allocation completeness rules (CR-228 D: previously unmapped → advisory fall-through)
|
|
41
43
|
'R-18': 'arch', 'R-19': 'ver', 'R-20': 'arch', 'R-21': 'ver',
|
|
42
|
-
'R-22': 'alloc', 'R-23': 'alloc', 'R-26': 'schema',
|
|
44
|
+
'R-22': 'alloc', 'R-23': 'alloc', 'R-26': 'schema', 'R-27': 'arch',
|
|
43
45
|
// uc
|
|
44
46
|
'UC-01': 'uc', 'UC-02': 'uc', 'UC-03': 'uc', 'UC-04': 'uc',
|
|
45
47
|
'UC-05': 'uc', 'UC-06': 'uc',
|
|
@@ -52,7 +54,7 @@ export const RULE_TO_DIMENSION = {
|
|
|
52
54
|
// ver
|
|
53
55
|
'R-01': 'ver', 'R-05': 'ver',
|
|
54
56
|
// schema
|
|
55
|
-
'SC-
|
|
57
|
+
'SC-02': 'schema', // SC-01/SC-03 deleted (BOK-CR-026) — R-26 is the binding rule
|
|
56
58
|
// structural rules without primary dimension → assigned by closest concern
|
|
57
59
|
'R-08': 'arch',
|
|
58
60
|
// near-duplicate detection
|
package/dist/se/rules.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @sigloch/contracts/se — single source of truth for SE validation rules.
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod/v4';
|
|
7
|
-
import { ElementType, TraceType, TestRefSchema,
|
|
7
|
+
import { ElementType, TraceType, TestRefSchema, RealRefSchema } from './ontology.js';
|
|
8
8
|
import { isValidTrace } from './meta-model.js';
|
|
9
9
|
export const RuleSeverity = z.enum(['error', 'warning', 'info']);
|
|
10
10
|
/** Candidate target for resolving a violation (e.g. a REQ to satisfy, a TEST to link). */
|
|
@@ -411,6 +411,58 @@ function noPrematureDecomposition(graph) {
|
|
|
411
411
|
}));
|
|
412
412
|
}
|
|
413
413
|
// ---------------------------------------------------------------------------
|
|
414
|
+
// RD-04: Decomposition breadth (CR-SM-221)
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
/**
|
|
417
|
+
* Max children on one decomposition level. The 7–11 convention existed only as
|
|
418
|
+
* prose ("darüber func-of-func"); below 7 is a guideline, not a violation, so only
|
|
419
|
+
* the upper bound is a rule.
|
|
420
|
+
*
|
|
421
|
+
* Counted per (parent, kind) — a MOD that both holds 12 FUNCs and composes 12
|
|
422
|
+
* sub-MODs has two breadth problems, not one. (The spike reference merged both into
|
|
423
|
+
* a single counter on the MOD id and would have reported 24 under one kind.)
|
|
424
|
+
*/
|
|
425
|
+
const DECOMPOSITION_BREADTH_MAX = 11;
|
|
426
|
+
function decompositionBreadth(graph) {
|
|
427
|
+
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
428
|
+
const byId = new Map(graph.elements.map(e => [e.id, e]));
|
|
429
|
+
const counts = new Map();
|
|
430
|
+
const bump = (parentId, kind) => {
|
|
431
|
+
const key = `${parentId}${kind}`;
|
|
432
|
+
const entry = counts.get(key);
|
|
433
|
+
if (entry)
|
|
434
|
+
entry.n++;
|
|
435
|
+
else
|
|
436
|
+
counts.set(key, { parentId, kind, n: 1 });
|
|
437
|
+
};
|
|
438
|
+
for (const t of graph.traces) {
|
|
439
|
+
const src = typeOf.get(t.source);
|
|
440
|
+
const tgt = typeOf.get(t.target);
|
|
441
|
+
if (t.type === 'compose' && src === 'FUNC' && tgt === 'FUNC') {
|
|
442
|
+
bump(t.source, 'sub-FUNC');
|
|
443
|
+
}
|
|
444
|
+
else if (t.type === 'allocate' && tgt === 'MOD' && src === 'FUNC') {
|
|
445
|
+
bump(t.target, 'allocated FUNC');
|
|
446
|
+
}
|
|
447
|
+
else if (t.type === 'compose' && (src === 'SYS' || src === 'MOD') && tgt === 'MOD') {
|
|
448
|
+
bump(t.source, 'sub-MOD');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return [...counts.values()]
|
|
452
|
+
.filter(c => c.n > DECOMPOSITION_BREADTH_MAX)
|
|
453
|
+
.map(c => {
|
|
454
|
+
const parent = byId.get(c.parentId);
|
|
455
|
+
return {
|
|
456
|
+
rule_id: 'RD-04',
|
|
457
|
+
severity: 'warning',
|
|
458
|
+
element_id: c.parentId,
|
|
459
|
+
message: `${c.parentId} has ${c.n} ${c.kind} children on one level (>${DECOMPOSITION_BREADTH_MAX})`,
|
|
460
|
+
fix_hint: 'Introduce an intermediate level (func-of-func / sub-MOD)',
|
|
461
|
+
context: { element_type: parent?.type, element_name: parent?.name },
|
|
462
|
+
};
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
414
466
|
// R-14: UC must have at least 1 compose trace (→ FCHAIN or REQ) (CR-117)
|
|
415
467
|
// ---------------------------------------------------------------------------
|
|
416
468
|
function ucMustHaveCompose(graph) {
|
|
@@ -581,10 +633,10 @@ function testMustHaveRunnableBinding(graph) {
|
|
|
581
633
|
}));
|
|
582
634
|
}
|
|
583
635
|
// ---------------------------------------------------------------------------
|
|
584
|
-
// R-20: FUNC
|
|
585
|
-
// is not explicitly concept-only (attributes.concept === true) or
|
|
586
|
-
// realized (attributes.external === true) counts as BOUND when it EITHER
|
|
587
|
-
// a valid
|
|
636
|
+
// R-20: FUNC realRef binding (CR-GC-205 Item 5, extended by CR-210, unified CR-228)
|
|
637
|
+
// — a FUNC that is not explicitly concept-only (attributes.concept === true) or
|
|
638
|
+
// externally realized (attributes.external === true) counts as BOUND when it EITHER
|
|
639
|
+
// carries a valid realRef {file, symbol?} OR is a blackbox parent whose compose→FUNC
|
|
588
640
|
// children are ALL (recursively) bound — the parent is realized by its children
|
|
589
641
|
// (e.g. FUNC-gesture-capture, gve CR-GVE-150). A partially-bound parent fires
|
|
590
642
|
// R-20 naming the still-unbound leaves in the fix_hint, so drift on a child is
|
|
@@ -598,7 +650,7 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
598
650
|
const composeFuncChildren = (id) => graph.traces
|
|
599
651
|
.filter(t => t.source === id && t.type === 'compose' && funcById.has(t.target))
|
|
600
652
|
.map(t => t.target);
|
|
601
|
-
const hasCodeRef = (el) =>
|
|
653
|
+
const hasCodeRef = (el) => RealRefSchema.safeParse(el.attributes?.realRef).success;
|
|
602
654
|
const isExempt = (el) => el.attributes?.concept === true || el.attributes?.external === true;
|
|
603
655
|
// A FUNC is bound iff it is exempt, has a codeRef, or is a parent whose
|
|
604
656
|
// compose→FUNC children are all bound (recursive, cycle-safe).
|
|
@@ -662,8 +714,8 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
662
714
|
rule_id: 'R-20',
|
|
663
715
|
severity: 'warning',
|
|
664
716
|
element_id: fn.id,
|
|
665
|
-
message: `${fn.id} is a FUNC without a valid
|
|
666
|
-
fix_hint: 'Add attributes.
|
|
717
|
+
message: `${fn.id} is a FUNC without a valid realRef binding`,
|
|
718
|
+
fix_hint: 'Add attributes.realRef {file, symbol, lang?}, or set attributes.concept:true (spec-only) / attributes.external:true (realized in another package)',
|
|
667
719
|
context: { element_type: fn.type, element_name: fn.name },
|
|
668
720
|
});
|
|
669
721
|
}
|
|
@@ -800,28 +852,52 @@ function modMustHaveAllocatedFunc(graph) {
|
|
|
800
852
|
// MOD→satisfy→REQ, behavioral NFRs FCHAIN→satisfy→REQ. R-18 now flags any residual
|
|
801
853
|
// REQ→MOD allocate edge as an invalid trace pattern.
|
|
802
854
|
// ---------------------------------------------------------------------------
|
|
803
|
-
// R-26: SCHEMA
|
|
804
|
-
// concept-only or external should carry a valid
|
|
805
|
-
// Zod definition is machine-resolvable (RC-03 then checks it resolves, RC-04
|
|
806
|
-
// it is parsed at the interface). WARNING, not error:
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
855
|
+
// R-26: SCHEMA realRef presence (CR-211, unified CR-228) — a SCHEMA that is not
|
|
856
|
+
// explicitly concept-only or external should carry a valid realRef {file, symbol?}
|
|
857
|
+
// so its Zod definition is machine-resolvable (RC-03 then checks it resolves, RC-04
|
|
858
|
+
// that it is parsed at the interface). WARNING, not error: currently-unbound SCHEMAs
|
|
859
|
+
// on the reference model must not turn readiness red before any binding exists — the
|
|
860
|
+
// presence signal mirrors R-20 (FUNC realRef) / R-19 (testRef). Symbol RESOLUTION is
|
|
861
|
+
// out of scope here (pure, no I/O — that is RC-03's job).
|
|
810
862
|
// ---------------------------------------------------------------------------
|
|
811
863
|
function schemaMustHaveSchemaRef(graph) {
|
|
812
864
|
return graph.elements
|
|
813
865
|
.filter(e => e.type === 'SCHEMA')
|
|
814
866
|
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
815
|
-
.filter(e => !
|
|
867
|
+
.filter(e => !RealRefSchema.safeParse(e.attributes?.realRef).success)
|
|
816
868
|
.map(sc => ({
|
|
817
869
|
rule_id: 'R-26',
|
|
818
870
|
severity: 'warning',
|
|
819
871
|
element_id: sc.id,
|
|
820
|
-
message: `${sc.id} is a SCHEMA without a valid
|
|
821
|
-
fix_hint: 'Add attributes.
|
|
872
|
+
message: `${sc.id} is a SCHEMA without a valid realRef binding`,
|
|
873
|
+
fix_hint: 'Add attributes.realRef {file, symbol, lang?}, or set attributes.concept:true (spec-only) / attributes.external:true (foreign-API contract)',
|
|
822
874
|
context: { element_type: sc.type, element_name: sc.name },
|
|
823
875
|
}));
|
|
824
876
|
}
|
|
877
|
+
// ---------------------------------------------------------------------------
|
|
878
|
+
// R-27: physical MOD realRef presence (CR-228) — the MOD arm of the unified
|
|
879
|
+
// "element must have a realRef" rule (FUNC=R-20, SCHEMA=R-26, physical MOD=R-27).
|
|
880
|
+
// A physical MOD (kind='physical') is a Bauteil realized by a CAD/geometry
|
|
881
|
+
// artefact, not by code (RT-01: FUNCs never allocate directly to it). Logical/SW
|
|
882
|
+
// MODs are realized through their FUNCs' code (R-20) and are NOT in scope here.
|
|
883
|
+
// WARNING (presence signal like R-20/R-26); concept/external MODs are exempt.
|
|
884
|
+
// Symbol is optional on a realRef (a geometry file has none) — presence = a valid
|
|
885
|
+
// realRef with a file. RESOLUTION (file on disk) is a consumer/RC concern.
|
|
886
|
+
// ---------------------------------------------------------------------------
|
|
887
|
+
function physicalModMustHaveRealRef(graph) {
|
|
888
|
+
return graph.elements
|
|
889
|
+
.filter(e => e.type === 'MOD' && e.attributes?.kind === 'physical')
|
|
890
|
+
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
891
|
+
.filter(e => !RealRefSchema.safeParse(e.attributes?.realRef).success)
|
|
892
|
+
.map(mod => ({
|
|
893
|
+
rule_id: 'R-27',
|
|
894
|
+
severity: 'warning',
|
|
895
|
+
element_id: mod.id,
|
|
896
|
+
message: `${mod.id} is a physical MOD without a valid realRef (CAD/geometry) binding`,
|
|
897
|
+
fix_hint: 'Add attributes.realRef {file, symbol?, lang?} pointing at the CAD/geometry artefact, or set attributes.concept:true / attributes.external:true',
|
|
898
|
+
context: { element_type: mod.type, element_name: mod.name },
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
825
901
|
export const V3_RULES = [
|
|
826
902
|
{ id: 'R-01', name: 'REQ must have verification', severity: 'error', evaluate: reqMustHaveVerification },
|
|
827
903
|
{ id: 'R-02', name: 'FUNC must satisfy REQ', severity: 'warning', evaluate: funcMustSatisfyReq },
|
|
@@ -837,14 +913,16 @@ export const V3_RULES = [
|
|
|
837
913
|
{ id: 'R-12', name: 'No circular dependencies', severity: 'warning', evaluate: noDirectCircular },
|
|
838
914
|
{ id: 'R-18', name: 'Valid trace pattern', severity: 'error', evaluate: validTracePattern },
|
|
839
915
|
{ id: 'R-19', name: 'Runnable TEST binding', severity: 'warning', evaluate: testMustHaveRunnableBinding },
|
|
840
|
-
{ id: 'R-20', name: 'FUNC
|
|
916
|
+
{ id: 'R-20', name: 'FUNC realRef binding', severity: 'warning', evaluate: funcMustHaveCodeBinding },
|
|
841
917
|
{ id: 'R-21', name: 'FUNC↔FUNC connection needs integration test', severity: 'warning', evaluate: fchainMustHaveIntegrationTest },
|
|
842
918
|
{ id: 'R-22', name: 'FUNC must be allocated to MOD', severity: 'warning', evaluate: funcMustBeAllocated },
|
|
843
919
|
{ id: 'R-23', name: 'MOD must have allocated FUNC', severity: 'warning', evaluate: modMustHaveAllocatedFunc },
|
|
844
|
-
{ id: 'R-26', name: 'SCHEMA must have
|
|
920
|
+
{ id: 'R-26', name: 'SCHEMA must have realRef', severity: 'warning', evaluate: schemaMustHaveSchemaRef },
|
|
921
|
+
{ id: 'R-27', name: 'Physical MOD must have realRef', severity: 'warning', evaluate: physicalModMustHaveRealRef },
|
|
845
922
|
{ id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', evaluate: unresolvedRequirement },
|
|
846
923
|
{ id: 'RD-02', name: 'Decomposition consistency', severity: 'warning', evaluate: decompositionConsistency },
|
|
847
924
|
{ id: 'RD-03', name: 'No premature decomposition', severity: 'info', evaluate: noPrematureDecomposition },
|
|
925
|
+
{ id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', evaluate: decompositionBreadth },
|
|
848
926
|
{ id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', evaluate: msEmptyScope },
|
|
849
927
|
{ id: 'MS-02', name: 'Milestone dangling dependency', severity: 'error', evaluate: msDanglingDependency },
|
|
850
928
|
];
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SC-
|
|
3
|
-
*
|
|
2
|
+
* SC-02 — Schema Quality rule (CR-122, reduced by BOK-CR-026).
|
|
3
|
+
*
|
|
4
|
+
* SC-01 (`zodDefinition` present) and SC-03 (`sourceFile`/`sourceExport` present) were
|
|
5
|
+
* DELETED: both re-asked R-26's question ("is this SCHEMA bound to code?") on legacy
|
|
6
|
+
* attributes and fired on exactly the same elements (Jaccard 1.0 vs R-26). SC-03 had
|
|
7
|
+
* additionally become wrong — a SCHEMA correctly bound via `realRef` still fired it.
|
|
8
|
+
* `realRef` is the single SCHEMA binding truth (R-26 presence, RC-03/RC-04 resolution);
|
|
9
|
+
* a spec-only SCHEMA is `concept:true`, it does NOT carry a Zod body copy in the graph
|
|
10
|
+
* (same rule as TEST: bindings point at code, they never mirror it).
|
|
11
|
+
*
|
|
12
|
+
* What remains here is the one SCHEMA check that is NOT about binding: is the interface
|
|
13
|
+
* used by any FLOW.
|
|
4
14
|
*/
|
|
5
15
|
import type { OntologyGraph } from './ontology.js';
|
|
6
16
|
import type { RuleDefinition, RuleViolation } from './rules.js';
|
|
7
|
-
export declare function sc01HasDefinition(graph: OntologyGraph): RuleViolation[];
|
|
8
17
|
export declare function sc02IsReferenced(graph: OntologyGraph): RuleViolation[];
|
|
9
|
-
export declare function sc03IsImplemented(graph: OntologyGraph): RuleViolation[];
|
|
10
18
|
export declare const SC_RULES: RuleDefinition[];
|
|
11
19
|
export declare function evaluateSCRules(graph: OntologyGraph): RuleViolation[];
|
|
@@ -1,26 +1,4 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// SC-01: SCHEMA must have zodDefinition attribute
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
export function sc01HasDefinition(graph) {
|
|
5
|
-
return graph.elements
|
|
6
|
-
.filter(e => e.type === 'SCHEMA')
|
|
7
|
-
.filter(s => {
|
|
8
|
-
const def = s.attributes?.zodDefinition;
|
|
9
|
-
return !def || (typeof def === 'string' && def.trim().length === 0);
|
|
10
|
-
})
|
|
11
|
-
.map(s => ({
|
|
12
|
-
rule_id: 'SC-01',
|
|
13
|
-
severity: 'warning',
|
|
14
|
-
element_id: s.id,
|
|
15
|
-
message: `${s.id} has no zodDefinition attribute`,
|
|
16
|
-
fix_hint: 'Add zodDefinition attribute with Zod schema string',
|
|
17
|
-
context: {
|
|
18
|
-
element_type: s.type,
|
|
19
|
-
element_name: s.name,
|
|
20
|
-
},
|
|
21
|
-
}));
|
|
22
|
-
}
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
2
|
// SC-02: SCHEMA must be referenced by at least one FLOW via relation trace
|
|
25
3
|
// ---------------------------------------------------------------------------
|
|
26
4
|
export function sc02IsReferenced(graph) {
|
|
@@ -42,31 +20,10 @@ export function sc02IsReferenced(graph) {
|
|
|
42
20
|
}));
|
|
43
21
|
}
|
|
44
22
|
// ---------------------------------------------------------------------------
|
|
45
|
-
// SC-03: SCHEMA should have sourceFile (implementation marker)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
export function sc03IsImplemented(graph) {
|
|
48
|
-
return graph.elements
|
|
49
|
-
.filter(e => e.type === 'SCHEMA')
|
|
50
|
-
.filter(s => !s.attributes?.sourceFile && !s.attributes?.sourceExport)
|
|
51
|
-
.map(s => ({
|
|
52
|
-
rule_id: 'SC-03',
|
|
53
|
-
severity: 'info',
|
|
54
|
-
element_id: s.id,
|
|
55
|
-
message: `${s.id} has no sourceFile/sourceExport (not implemented yet)`,
|
|
56
|
-
fix_hint: 'Implement the Zod schema in code and set sourceFile/sourceExport',
|
|
57
|
-
context: {
|
|
58
|
-
element_type: s.type,
|
|
59
|
-
element_name: s.name,
|
|
60
|
-
},
|
|
61
|
-
}));
|
|
62
|
-
}
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
23
|
// Aggregated array & convenience runner
|
|
65
24
|
// ---------------------------------------------------------------------------
|
|
66
25
|
export const SC_RULES = [
|
|
67
|
-
{ id: 'SC-01', name: 'Schema has definition', severity: 'warning', evaluate: sc01HasDefinition },
|
|
68
26
|
{ id: 'SC-02', name: 'Schema referenced by FLOW', severity: 'warning', evaluate: sc02IsReferenced },
|
|
69
|
-
{ id: 'SC-03', name: 'Schema is implemented', severity: 'info', evaluate: sc03IsImplemented },
|
|
70
27
|
];
|
|
71
28
|
export function evaluateSCRules(graph) {
|
|
72
29
|
return SC_RULES.flatMap(rule => rule.evaluate(graph));
|
package/package.json
CHANGED
package/dist/se/semantic-id.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SemanticId — Format-E canonical element ID schema.
|
|
3
|
-
* Aligned with graphengine CR-052/CR-053.
|
|
4
|
-
*
|
|
5
|
-
* Format: {Name}.{TypeAbbr}.{Counter}
|
|
6
|
-
* Example: ObserveSessions.UC.001
|
|
7
|
-
*
|
|
8
|
-
* @sigloch/contracts/se
|
|
9
|
-
*/
|
|
10
|
-
import { z } from 'zod/v4';
|
|
11
|
-
import type { ElementType } from './ontology.js';
|
|
12
|
-
/** 2-char abbreviation → full ElementType. */
|
|
13
|
-
export declare const ABBREV_TO_TYPE: Record<string, ElementType>;
|
|
14
|
-
/** Full ElementType → 2-char abbreviation. */
|
|
15
|
-
export declare const TYPE_TO_ABBREV: Record<string, string>;
|
|
16
|
-
/** SemanticId: `Name.TypeAbbr.Counter` where Counter is 3-digit or 6-char alphanumeric. */
|
|
17
|
-
export declare const SemanticId: z.ZodString;
|
|
18
|
-
export type SemanticId = z.infer<typeof SemanticId>;
|
|
19
|
-
/** Check if a string is a valid SemanticId. */
|
|
20
|
-
export declare function isSemanticId(s: string): boolean;
|
|
21
|
-
/** Extract name, type, and counter from a SemanticId. */
|
|
22
|
-
export declare function extractFromSemanticId(semanticId: string): {
|
|
23
|
-
name: string;
|
|
24
|
-
type: ElementType;
|
|
25
|
-
counter: string;
|
|
26
|
-
};
|
|
27
|
-
/** Build a SemanticId from parts. */
|
|
28
|
-
export declare function toSemanticId(name: string, type: ElementType, counter: string): string;
|
|
29
|
-
/** Get the 2-char abbreviation for an ElementType. */
|
|
30
|
-
export declare function getTypeAbbreviation(type: ElementType): string;
|
package/dist/se/semantic-id.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SemanticId — Format-E canonical element ID schema.
|
|
3
|
-
* Aligned with graphengine CR-052/CR-053.
|
|
4
|
-
*
|
|
5
|
-
* Format: {Name}.{TypeAbbr}.{Counter}
|
|
6
|
-
* Example: ObserveSessions.UC.001
|
|
7
|
-
*
|
|
8
|
-
* @sigloch/contracts/se
|
|
9
|
-
*/
|
|
10
|
-
import { z } from 'zod/v4';
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// Type abbreviation maps (aligned with graphengine semantic-id.ts)
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
/** 2-char abbreviation → full ElementType. */
|
|
15
|
-
export const ABBREV_TO_TYPE = {
|
|
16
|
-
SY: 'SYS',
|
|
17
|
-
UC: 'UC',
|
|
18
|
-
RQ: 'REQ',
|
|
19
|
-
FN: 'FUNC',
|
|
20
|
-
FC: 'FCHAIN',
|
|
21
|
-
FL: 'FLOW',
|
|
22
|
-
AC: 'ACTOR',
|
|
23
|
-
MD: 'MOD',
|
|
24
|
-
TC: 'TEST',
|
|
25
|
-
TS: 'TEST', // legacy alias
|
|
26
|
-
SC: 'SCHEMA',
|
|
27
|
-
CR: 'CR',
|
|
28
|
-
MS: 'MS',
|
|
29
|
-
};
|
|
30
|
-
/** Full ElementType → 2-char abbreviation. */
|
|
31
|
-
export const TYPE_TO_ABBREV = {
|
|
32
|
-
SYS: 'SY',
|
|
33
|
-
UC: 'UC',
|
|
34
|
-
REQ: 'RQ',
|
|
35
|
-
FUNC: 'FN',
|
|
36
|
-
FCHAIN: 'FC',
|
|
37
|
-
FLOW: 'FL',
|
|
38
|
-
ACTOR: 'AC',
|
|
39
|
-
MOD: 'MD',
|
|
40
|
-
TEST: 'TC',
|
|
41
|
-
SCHEMA: 'SC',
|
|
42
|
-
SESSION: 'SE',
|
|
43
|
-
CR: 'CR',
|
|
44
|
-
MS: 'MS',
|
|
45
|
-
};
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Zod schema
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
/** SemanticId: `Name.TypeAbbr.Counter` where Counter is 3-digit or 6-char alphanumeric. */
|
|
50
|
-
export const SemanticId = z.string().regex(/^[A-Za-z][A-Za-z0-9_+]{0,49}\.[A-Z]{2}\.[a-z0-9]{3,6}$/, 'SemanticId must match {Name}.{TypeAbbr}.{Counter}');
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Pure functions
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
/** Check if a string is a valid SemanticId. */
|
|
55
|
-
export function isSemanticId(s) {
|
|
56
|
-
return SemanticId.safeParse(s).success;
|
|
57
|
-
}
|
|
58
|
-
/** Extract name, type, and counter from a SemanticId. */
|
|
59
|
-
export function extractFromSemanticId(semanticId) {
|
|
60
|
-
const parts = semanticId.split('.');
|
|
61
|
-
if (parts.length < 3) {
|
|
62
|
-
throw new Error(`Invalid semanticId: "${semanticId}" — expected Name.TypeAbbr.Counter`);
|
|
63
|
-
}
|
|
64
|
-
const name = parts[0];
|
|
65
|
-
const typeAbbr = parts[1];
|
|
66
|
-
const type = ABBREV_TO_TYPE[typeAbbr];
|
|
67
|
-
if (!type) {
|
|
68
|
-
throw new Error(`Unknown type abbreviation "${typeAbbr}" in: "${semanticId}"`);
|
|
69
|
-
}
|
|
70
|
-
return { name, type, counter: parts[2] };
|
|
71
|
-
}
|
|
72
|
-
/** Build a SemanticId from parts. */
|
|
73
|
-
export function toSemanticId(name, type, counter) {
|
|
74
|
-
const abbr = TYPE_TO_ABBREV[type];
|
|
75
|
-
if (!abbr)
|
|
76
|
-
throw new Error(`No abbreviation for type: ${type}`);
|
|
77
|
-
const sanitized = sanitizeName(name);
|
|
78
|
-
return `${sanitized}.${abbr}.${counter}`;
|
|
79
|
-
}
|
|
80
|
-
/** Get the 2-char abbreviation for an ElementType. */
|
|
81
|
-
export function getTypeAbbreviation(type) {
|
|
82
|
-
return TYPE_TO_ABBREV[type] ?? type.slice(0, 2).toUpperCase();
|
|
83
|
-
}
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Helpers
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
/** Sanitize a name for SemanticId: PascalCase, only alnum + underscore + plus, max 50 chars. */
|
|
88
|
-
function sanitizeName(name) {
|
|
89
|
-
return name.replace(/[^a-zA-Z0-9_+]/g, '').slice(0, 50);
|
|
90
|
-
}
|