@sigloch/contracts 6.1.0 → 6.3.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/ao-rules.js +44 -29
- package/dist/se/conformance-rules.d.ts +1 -0
- package/dist/se/conformance-rules.js +67 -0
- package/dist/se/fchain-quality-rules.js +57 -53
- package/dist/se/flat-graph.d.ts +3 -0
- package/dist/se/flat-graph.js +52 -0
- package/dist/se/grammar-snapshot.d.ts +2 -1
- package/dist/se/grammar-snapshot.js +9 -1
- package/dist/se/graph-index.d.ts +56 -0
- package/dist/se/graph-index.js +60 -0
- package/dist/se/index.d.ts +3 -1
- package/dist/se/index.js +3 -1
- package/dist/se/metric-rules.d.ts +8 -0
- package/dist/se/metric-rules.js +95 -23
- package/dist/se/rules.d.ts +12 -0
- package/dist/se/rules.js +155 -95
- package/dist/se/uc-quality-rules.js +46 -23
- package/package.json +1 -1
package/dist/se/ao-rules.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getND02SimilarityMatrix } from './near-duplicate-rules.js';
|
|
2
|
+
import { indexOf } from './graph-index.js';
|
|
2
3
|
const SCHEMA_OVERLAP_THRESHOLD = 0.5;
|
|
3
4
|
/**
|
|
4
5
|
* AO-D01: Relay Node Detection.
|
|
@@ -6,13 +7,14 @@ const SCHEMA_OVERLAP_THRESHOLD = 0.5;
|
|
|
6
7
|
* and (c) target FUNCs share SCHEMA overlap >= 0.5 (via ND-02 matrix, skipped if unavailable).
|
|
7
8
|
*/
|
|
8
9
|
export function aoD01RelayNode(graph) {
|
|
10
|
+
const idx = indexOf(graph);
|
|
9
11
|
const violations = [];
|
|
10
|
-
const funcs =
|
|
12
|
+
const funcs = idx.elementsOfType('FUNC');
|
|
11
13
|
const funcIdSet = new Set(funcs.map(f => f.id));
|
|
12
14
|
for (const func of funcs) {
|
|
13
15
|
// (a) No satisfy traces (FUNC as source) connecting to any REQ
|
|
14
16
|
const hasSatisfy = graph.traces.some(t => t.type === 'satisfy' &&
|
|
15
|
-
t.source === func.id &&
|
|
17
|
+
t.source === func.id && idx.typeOf(t.target) === 'REQ');
|
|
16
18
|
if (hasSatisfy)
|
|
17
19
|
continue;
|
|
18
20
|
// (b) >=2 outgoing io traces to other FUNCs
|
|
@@ -24,7 +26,7 @@ export function aoD01RelayNode(graph) {
|
|
|
24
26
|
// (c) Target FUNCs share SCHEMA overlap >= 0.5 (skip check if no matrix)
|
|
25
27
|
if (!passesSchemaOverlap(graph, ioTargets))
|
|
26
28
|
continue;
|
|
27
|
-
const el =
|
|
29
|
+
const el = idx.byId.get(func.id);
|
|
28
30
|
violations.push({
|
|
29
31
|
rule_id: 'AO-D01',
|
|
30
32
|
severity: 'info',
|
|
@@ -37,29 +39,35 @@ export function aoD01RelayNode(graph) {
|
|
|
37
39
|
}
|
|
38
40
|
/** CR-165: Resolve FUNC→SCHEMA via FUNC→FLOW(io) + FLOW→SCHEMA(relation). */
|
|
39
41
|
function funcToSchemas(graph, funcId) {
|
|
42
|
+
const idx = indexOf(graph);
|
|
40
43
|
const schemas = new Set();
|
|
41
44
|
// Find FLOWs connected to this FUNC via io (FUNC→FLOW or FLOW→FUNC)
|
|
45
|
+
//
|
|
46
|
+
// CR-SM-264: die io-Kanten dieser FUNC statt zweier Vollscans ueber ALLE Traces je FUNC
|
|
47
|
+
// (`F x T`) — AO-D03 ruft diese Funktion einmal je FUNC. Die Reihenfolge von `flowIds` ist
|
|
48
|
+
// dabei egal, die Menge wird nur auf Mitgliedschaft geprueft.
|
|
42
49
|
const flowIds = new Set();
|
|
43
|
-
for (const t of
|
|
44
|
-
if (t.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
for (const t of idx.out(funcId, 'io'))
|
|
51
|
+
if (idx.typeOf(t.target) === 'FLOW')
|
|
52
|
+
flowIds.add(t.target);
|
|
53
|
+
for (const t of idx.in(funcId, 'io'))
|
|
54
|
+
if (idx.typeOf(t.source) === 'FLOW')
|
|
55
|
+
flowIds.add(t.source);
|
|
56
|
+
// Find SCHEMAs connected to those FLOWs via relation.
|
|
57
|
+
//
|
|
58
|
+
// Hier NICHT ueber `flowIds` iterieren, obwohl das billiger waere: die Einfuegereihenfolge
|
|
59
|
+
// dieser Menge landet ueber `shared.join(', ')` woertlich in der AO-D03-Meldung. Der Lauf
|
|
60
|
+
// ueber die relation-Kanten in Graph-Reihenfolge haelt sie identisch — und ist trotzdem
|
|
61
|
+
// deutlich kuerzer als der Vollscan, weil er nur einen Kantentyp sieht.
|
|
62
|
+
for (const t of idx.tracesOfType('relation')) {
|
|
63
|
+
if (flowIds.has(t.source) && idx.typeOf(t.target) === 'SCHEMA')
|
|
56
64
|
schemas.add(t.target);
|
|
57
|
-
}
|
|
58
65
|
}
|
|
59
66
|
return schemas;
|
|
60
67
|
}
|
|
61
68
|
/** Check if any pair of target FUNCs shares SCHEMA overlap via FLOW→SCHEMA(relation) + ND-02 matrix. */
|
|
62
69
|
function passesSchemaOverlap(graph, targetFuncIds) {
|
|
70
|
+
const idx = indexOf(graph);
|
|
63
71
|
const nd02 = getND02SimilarityMatrix();
|
|
64
72
|
if (!nd02)
|
|
65
73
|
return true; // no matrix → skip check, assume pass
|
|
@@ -93,8 +101,9 @@ function passesSchemaOverlap(graph, targetFuncIds) {
|
|
|
93
101
|
* FUNC A sends io to both B and C, where B and C connect to the same SCHEMA elements.
|
|
94
102
|
*/
|
|
95
103
|
export function aoD03DuplicatePath(graph) {
|
|
104
|
+
const idx = indexOf(graph);
|
|
96
105
|
const violations = [];
|
|
97
|
-
const funcs =
|
|
106
|
+
const funcs = idx.elementsOfType('FUNC');
|
|
98
107
|
const funcIdSet = new Set(funcs.map(f => f.id));
|
|
99
108
|
const seen = new Set();
|
|
100
109
|
// Pre-compute: FUNC → set of SCHEMA targets via FLOW→SCHEMA(relation) (CR-165)
|
|
@@ -141,15 +150,16 @@ export function aoD03DuplicatePath(graph) {
|
|
|
141
150
|
// diesen Aus-Zustand nicht: jedes Modulpaar mit >= 1 Kreuzung erzeugte eine info-Meldung.
|
|
142
151
|
// ---------------------------------------------------------------------------
|
|
143
152
|
export function cr01CrossingFlowCount(graph, policy) {
|
|
153
|
+
const idx = indexOf(graph);
|
|
144
154
|
const violations = [];
|
|
145
155
|
const steps = policy.crossingFlows;
|
|
146
156
|
if (steps === null)
|
|
147
157
|
return violations;
|
|
148
|
-
const mods =
|
|
158
|
+
const mods = idx.elementsOfType('MOD');
|
|
149
159
|
// Build MOD → Set<FUNC-id> mapping
|
|
150
160
|
const modFuncs = new Map();
|
|
151
161
|
for (const mod of mods) {
|
|
152
|
-
const funcIds = new Set(
|
|
162
|
+
const funcIds = new Set(idx.in(mod.id, 'allocate').map(t => t.source));
|
|
153
163
|
modFuncs.set(mod.id, funcIds);
|
|
154
164
|
}
|
|
155
165
|
// Count crossings per module pair
|
|
@@ -191,10 +201,11 @@ export function cr01CrossingFlowCount(graph, policy) {
|
|
|
191
201
|
// FUNCs must not be allocated directly to physical MODs — use logical sub-modules.
|
|
192
202
|
// ---------------------------------------------------------------------------
|
|
193
203
|
export function rt01PhysicalBoundaryIntegrity(graph) {
|
|
204
|
+
const idx = indexOf(graph);
|
|
194
205
|
const physicalMods = graph.elements.filter(e => e.type === 'MOD' && e.attributes?.kind === 'physical');
|
|
195
206
|
return graph.traces
|
|
196
207
|
.filter(t => t.type === 'allocate' && physicalMods.some(m => m.id === t.target))
|
|
197
|
-
.filter(t =>
|
|
208
|
+
.filter(t => idx.typeOf(t.source) === 'FUNC')
|
|
198
209
|
.map(t => ({
|
|
199
210
|
rule_id: 'RT-01',
|
|
200
211
|
severity: 'error',
|
|
@@ -208,10 +219,11 @@ export function rt01PhysicalBoundaryIntegrity(graph) {
|
|
|
208
219
|
// Physical MODs should have at least one logical sub-module via compose trace.
|
|
209
220
|
// ---------------------------------------------------------------------------
|
|
210
221
|
export function ph01PhysicalModCompleteness(graph) {
|
|
222
|
+
const idx = indexOf(graph);
|
|
211
223
|
const physicalMods = graph.elements.filter(e => e.type === 'MOD' && e.attributes?.kind === 'physical');
|
|
212
224
|
return physicalMods
|
|
213
225
|
.filter(pm => !graph.traces.some(t => t.source === pm.id && t.type === 'compose' &&
|
|
214
|
-
|
|
226
|
+
idx.typeOf(t.target) === 'MOD'))
|
|
215
227
|
.map(pm => ({
|
|
216
228
|
rule_id: 'PH-01',
|
|
217
229
|
severity: 'info',
|
|
@@ -226,6 +238,7 @@ export function ph01PhysicalModCompleteness(graph) {
|
|
|
226
238
|
// FUNC @requires must be satisfied by the physical MOD's @capability.
|
|
227
239
|
// ---------------------------------------------------------------------------
|
|
228
240
|
export function ca01CapabilityAllocation(graph) {
|
|
241
|
+
const idx = indexOf(graph);
|
|
229
242
|
const violations = [];
|
|
230
243
|
const funcsWithRequires = graph.elements.filter(e => e.type === 'FUNC' && e.attributes?.requires);
|
|
231
244
|
for (const func of funcsWithRequires) {
|
|
@@ -236,15 +249,16 @@ export function ca01CapabilityAllocation(graph) {
|
|
|
236
249
|
const allocTrace = graph.traces.find(t => t.source === func.id && t.type === 'allocate');
|
|
237
250
|
if (!allocTrace)
|
|
238
251
|
continue;
|
|
239
|
-
const
|
|
252
|
+
const logicalModCandidate = idx.byId.get(allocTrace.target);
|
|
253
|
+
const logicalMod = logicalModCandidate?.type === 'MOD' ? logicalModCandidate : undefined;
|
|
240
254
|
if (!logicalMod)
|
|
241
255
|
continue;
|
|
242
256
|
// Find physical parent MOD (compose source → logical MOD target)
|
|
243
257
|
const physParentTrace = graph.traces.find(t => t.type === 'compose' && t.target === logicalMod.id &&
|
|
244
|
-
|
|
258
|
+
(idx.byId.get(t.source)?.type === 'MOD' && idx.byId.get(t.source)?.attributes?.kind === 'physical'));
|
|
245
259
|
if (!physParentTrace)
|
|
246
260
|
continue;
|
|
247
|
-
const physMod =
|
|
261
|
+
const physMod = idx.byId.get(physParentTrace.source);
|
|
248
262
|
const capabilities = (Array.isArray(physMod.attributes?.capability)
|
|
249
263
|
? physMod.attributes.capability
|
|
250
264
|
: physMod.attributes?.capability ? [physMod.attributes.capability] : []);
|
|
@@ -289,9 +303,10 @@ export function ca01CapabilityAllocation(graph) {
|
|
|
289
303
|
// andere Regel trifft.
|
|
290
304
|
// ---------------------------------------------------------------------------
|
|
291
305
|
export function io01CrossModuleCompleteness(graph) {
|
|
306
|
+
const idx = indexOf(graph);
|
|
292
307
|
const violations = [];
|
|
293
308
|
const flowIds = new Set(graph.elements.filter(e => e.type === 'FLOW').map(e => e.id));
|
|
294
|
-
const funcIdSet =
|
|
309
|
+
const funcIdSet = idx.idsOfType('FUNC');
|
|
295
310
|
/** io-Nachbarschaft FUNC <-> FLOW, richtungslos: die Frage ist "haengt das zusammen?". */
|
|
296
311
|
const flowsOfFunc = new Map();
|
|
297
312
|
for (const t of graph.traces) {
|
|
@@ -306,7 +321,7 @@ export function io01CrossModuleCompleteness(graph) {
|
|
|
306
321
|
flowsOfFunc.set(f, new Set());
|
|
307
322
|
flowsOfFunc.get(f).add(fl);
|
|
308
323
|
}
|
|
309
|
-
for (const fc of
|
|
324
|
+
for (const fc of idx.elementsOfType('FCHAIN')) {
|
|
310
325
|
const members = graph.traces
|
|
311
326
|
.filter(t => t.source === fc.id && t.type === 'compose')
|
|
312
327
|
.map(t => t.target)
|
|
@@ -356,7 +371,7 @@ export function io01CrossModuleCompleteness(graph) {
|
|
|
356
371
|
for (const id of wired) {
|
|
357
372
|
if (largestSet.has(id))
|
|
358
373
|
continue;
|
|
359
|
-
const el =
|
|
374
|
+
const el = idx.byId.get(id);
|
|
360
375
|
const mod = graph.traces.find(t => t.source === id && t.type === 'allocate')?.target ?? 'unallocated';
|
|
361
376
|
violations.push({
|
|
362
377
|
rule_id: 'IO-01',
|
|
@@ -368,7 +383,7 @@ export function io01CrossModuleCompleteness(graph) {
|
|
|
368
383
|
element_type: 'FUNC',
|
|
369
384
|
element_name: el?.name ?? id,
|
|
370
385
|
candidate_targets: largest.slice(0, 3).map(t => {
|
|
371
|
-
const te =
|
|
386
|
+
const te = idx.byId.get(t);
|
|
372
387
|
return { id: t, type: te?.type ?? 'FUNC', name: te?.name ?? t };
|
|
373
388
|
}),
|
|
374
389
|
},
|
|
@@ -49,6 +49,7 @@ export declare const CodeFactsSchema: z.ZodObject<{
|
|
|
49
49
|
from: z.ZodString;
|
|
50
50
|
to: z.ZodString;
|
|
51
51
|
}, z.core.$strip>>>;
|
|
52
|
+
declaredDependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
52
53
|
}, z.core.$strip>;
|
|
53
54
|
export type CodeFacts = z.infer<typeof CodeFactsSchema>;
|
|
54
55
|
/** A conformance rule: pure over (graph, facts) — never touches I/O itself. */
|
|
@@ -48,6 +48,17 @@ export const CodeFactsSchema = z.object({
|
|
|
48
48
|
* given), so pre-CR-212 CodeFacts stay valid.
|
|
49
49
|
*/
|
|
50
50
|
importEdges: z.array(ImportEdgeSchema).optional(),
|
|
51
|
+
/**
|
|
52
|
+
* Package names the consumer declares in `dependencies` + `devDependencies`, for
|
|
53
|
+
* RC-06 (CR-SM-262). The extractor reads them from the repo's package.json.
|
|
54
|
+
*
|
|
55
|
+
* Optional, and the ABSENT case is silence, not a violation — the opposite of `files`,
|
|
56
|
+
* where a missing key must surface loudly. The asymmetry is deliberate: a missing file
|
|
57
|
+
* entry means the extractor looked and found nothing, while an absent dependency list
|
|
58
|
+
* means it never looked. Treating "never looked" as "declares nothing" would report
|
|
59
|
+
* every external binding in the graph at once, which is noise, not a finding.
|
|
60
|
+
*/
|
|
61
|
+
declaredDependencies: z.array(z.string()).optional(),
|
|
51
62
|
});
|
|
52
63
|
const missingFile = (facts, file) => facts.files[file]?.exists !== true;
|
|
53
64
|
// RC-01: every valid FUNC realRef must resolve — file on disk, symbol declared in
|
|
@@ -359,6 +370,61 @@ function importDriftConformance(graph, facts) {
|
|
|
359
370
|
};
|
|
360
371
|
});
|
|
361
372
|
}
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
// RC-06: an `external` realRef names a package the consumer actually depends on
|
|
375
|
+
// (CR-SM-262).
|
|
376
|
+
//
|
|
377
|
+
// RC-01..03 skip `external === true` — and rightly so: the path of a foreign package is
|
|
378
|
+
// not in this repo and cannot be resolved here. The consequence was that external bindings
|
|
379
|
+
// were never checked at all and rotted in silence. Found by hand at the graphcode
|
|
380
|
+
// self-model on 2026-08-22, not by a rule: `SCHEMA-metric-vector` carried
|
|
381
|
+
// `realRef.file = "packages/se-optimizer/src/metrics.ts"`, and `@sigloch/se-optimizer` had
|
|
382
|
+
// ceased to exist with CR-SM-248 — `MetricVector` moved to `se-engine`. The node pointed at
|
|
383
|
+
// a package that is not there, and RC-01 said nothing because `external: true` was set.
|
|
384
|
+
//
|
|
385
|
+
// The PATH is unverifiable here; the PACKAGE NAME is not. A path of the shape
|
|
386
|
+
// `packages/<name>/…` claims a workspace package, so `@sigloch/<name>` must appear in the
|
|
387
|
+
// consumer's dependencies. Any other path shape stays unchecked rather than guessed.
|
|
388
|
+
//
|
|
389
|
+
// Severity `warning`, not `error`: the path may deviate from the convention for honest
|
|
390
|
+
// reasons (monorepo restructuring, a linked working copy), and an `error` would block every
|
|
391
|
+
// further mutation through the delta gate. A rotted binding is a completeness signal, not a
|
|
392
|
+
// mismeasurement — unlike R-29, where the number itself becomes wrong.
|
|
393
|
+
// ---------------------------------------------------------------------------
|
|
394
|
+
/** `packages/<name>/…` — the workspace-package path shape RC-06 can decide. */
|
|
395
|
+
const WORKSPACE_PATH = /^packages\/([^/]+)\//;
|
|
396
|
+
/** Package scope of this family. A path claims `<SCOPE>/<name>`. */
|
|
397
|
+
const WORKSPACE_SCOPE = '@sigloch';
|
|
398
|
+
function externalRefMustNameDependency(graph, facts) {
|
|
399
|
+
const declared = facts.declaredDependencies;
|
|
400
|
+
if (declared === undefined)
|
|
401
|
+
return []; // extractor supplied none — silence, s. CodeFactsSchema
|
|
402
|
+
const known = new Set(declared);
|
|
403
|
+
const violations = [];
|
|
404
|
+
for (const el of graph.elements) {
|
|
405
|
+
// `concept: true` has no binding that could rot — a concept node claims nothing about code.
|
|
406
|
+
if (el.attributes?.external !== true || el.attributes?.concept === true)
|
|
407
|
+
continue;
|
|
408
|
+
const parsed = RealRefSchema.safeParse(el.attributes?.realRef);
|
|
409
|
+
if (!parsed.success)
|
|
410
|
+
continue;
|
|
411
|
+
const match = WORKSPACE_PATH.exec(parsed.data.file);
|
|
412
|
+
if (!match)
|
|
413
|
+
continue; // not the shape this rule can decide
|
|
414
|
+
const pkg = `${WORKSPACE_SCOPE}/${match[1]}`;
|
|
415
|
+
if (known.has(pkg))
|
|
416
|
+
continue;
|
|
417
|
+
violations.push({
|
|
418
|
+
rule_id: 'RC-06',
|
|
419
|
+
severity: 'warning',
|
|
420
|
+
element_id: el.id,
|
|
421
|
+
message: `${el.id} binds to '${parsed.data.file}', but '${pkg}' is not a declared dependency`,
|
|
422
|
+
fix_hint: `Add '${pkg}' to dependencies, or re-point the realRef at the package that now owns the symbol`,
|
|
423
|
+
context: { element_type: el.type, element_name: el.name },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return violations;
|
|
427
|
+
}
|
|
362
428
|
/** All RC conformance rules — evaluated by executors that can supply CodeFacts. */
|
|
363
429
|
export const CODE_CONFORMANCE_RULES = [
|
|
364
430
|
{ id: 'RC-01', name: 'FUNC realRef resolves to a declared symbol', severity: 'error', evaluate: codeRefMustResolve },
|
|
@@ -366,6 +432,7 @@ export const CODE_CONFORMANCE_RULES = [
|
|
|
366
432
|
{ id: 'RC-03', name: 'SCHEMA realRef resolves to a declared export', severity: 'error', evaluate: schemaRefMustResolve },
|
|
367
433
|
{ id: 'RC-04', name: 'SCHEMA realRef is parsed at its interface', severity: 'warning', evaluate: schemaRefMustBeUsed },
|
|
368
434
|
{ id: 'RC-05', name: 'cross-module import drift', severity: 'warning', evaluate: importDriftConformance },
|
|
435
|
+
{ id: 'RC-06', name: 'external realRef names a declared dependency', severity: 'warning', evaluate: externalRefMustNameDependency },
|
|
369
436
|
];
|
|
370
437
|
/** Run all RC rules against a graph + extracted code facts. */
|
|
371
438
|
export function evaluateConformanceRules(graph, facts) {
|
|
@@ -1,33 +1,36 @@
|
|
|
1
|
+
import { indexOf } from './graph-index.js';
|
|
1
2
|
// ---------------------------------------------------------------------------
|
|
2
3
|
// FC-01: FCHAIN must have Actor boundary (input or output via FLOW→ACTOR io)
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
export function fc01ActorBoundary(graph) {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
// CR-SM-264: dieselbe Frage, einmal statt je Kette neu.
|
|
7
|
+
//
|
|
8
|
+
// Die alte Fassung war die verschachtelte Form, die UC-02 kubisch gemacht hat (CR-SM-261):
|
|
9
|
+
// je FCHAIN alle Traces, darin je io-Kante noch einmal alle Traces, darin je Treffer alle
|
|
10
|
+
// Elemente — `FCHAIN x FUNC x T x T x E`. Im Profil nach Teil a war FC-01 mit 6,3 % die
|
|
11
|
+
// teuerste FCHAIN-Regel.
|
|
12
|
+
//
|
|
13
|
+
// Beide Zweige fragen in Wahrheit DASSELBE: "beruehrt dieses Element eine io-Kante, deren
|
|
14
|
+
// anderes Ende ein ACTOR ist?" Einmal fuer den Zwischenknoten der Kettenglieder, einmal
|
|
15
|
+
// direkt fuer den Eltern-UC. Eine Menge beantwortet beide.
|
|
16
|
+
const idx = indexOf(graph);
|
|
17
|
+
const actorAdjacent = new Set();
|
|
18
|
+
for (const t of idx.tracesOfType('io')) {
|
|
19
|
+
if (idx.typeOf(t.target) === 'ACTOR')
|
|
20
|
+
actorAdjacent.add(t.source);
|
|
21
|
+
if (idx.typeOf(t.source) === 'ACTOR')
|
|
22
|
+
actorAdjacent.add(t.target);
|
|
23
|
+
}
|
|
24
|
+
return idx.elementsOfType('FCHAIN')
|
|
7
25
|
.filter(fc => {
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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'))));
|
|
26
|
+
// Mitglieder der Kette — bewusst OHNE Typfilter, wie in der alten Fassung: FC-01 fragt
|
|
27
|
+
// nach dem, was komponiert ist, nicht nach dem, was eine FUNC ist (FC-03 tut das).
|
|
28
|
+
const memberIds = idx.out(fc.id, 'compose').map(t => t.target);
|
|
29
|
+
const hasActorIO = memberIds.some(fid => idx.out(fid, 'io').some(t => actorAdjacent.has(t.target)) ||
|
|
30
|
+
idx.in(fid, 'io').some(t => actorAdjacent.has(t.source)));
|
|
31
|
+
// Der Umweg ueber den Eltern-UC: dessen eigene ACTOR-io zaehlt auch.
|
|
32
|
+
const parentUC = idx.in(fc.id, 'compose').find(t => idx.typeOf(t.source) === 'UC');
|
|
33
|
+
const hasDirectActorIO = parentUC !== undefined && actorAdjacent.has(parentUC.source);
|
|
31
34
|
return !hasActorIO && !hasDirectActorIO;
|
|
32
35
|
})
|
|
33
36
|
.map(fc => ({
|
|
@@ -43,17 +46,15 @@ export function fc01ActorBoundary(graph) {
|
|
|
43
46
|
// FC-02: Leaf UC (no UC→compose→UC) must have at least one FCHAIN
|
|
44
47
|
// ---------------------------------------------------------------------------
|
|
45
48
|
export function fc02LeafUcHasFchain(graph) {
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const idx = indexOf(graph);
|
|
50
|
+
return idx.elementsOfType('UC')
|
|
48
51
|
.filter(uc => {
|
|
52
|
+
const composed = idx.out(uc.id, 'compose');
|
|
49
53
|
// Leaf = no compose→UC children
|
|
50
|
-
|
|
51
|
-
graph.elements.some(e => e.id === t.target && e.type === 'UC'));
|
|
52
|
-
if (hasUCChild)
|
|
54
|
+
if (composed.some(t => idx.typeOf(t.target) === 'UC'))
|
|
53
55
|
return false;
|
|
54
56
|
// Must have compose→FCHAIN
|
|
55
|
-
return !
|
|
56
|
-
graph.elements.some(e => e.id === t.target && e.type === 'FCHAIN'));
|
|
57
|
+
return !composed.some(t => idx.typeOf(t.target) === 'FCHAIN');
|
|
57
58
|
})
|
|
58
59
|
.map(uc => ({
|
|
59
60
|
rule_id: 'FC-02',
|
|
@@ -69,15 +70,13 @@ export function fc02LeafUcHasFchain(graph) {
|
|
|
69
70
|
// ---------------------------------------------------------------------------
|
|
70
71
|
export function fc03FchainFlat(graph) {
|
|
71
72
|
const violations = [];
|
|
72
|
-
const
|
|
73
|
-
for (const fc of
|
|
74
|
-
const funcIds =
|
|
75
|
-
.filter(t => t.source === fc.id && t.type === 'compose')
|
|
73
|
+
const idx = indexOf(graph);
|
|
74
|
+
for (const fc of idx.elementsOfType('FCHAIN')) {
|
|
75
|
+
const funcIds = idx.out(fc.id, 'compose')
|
|
76
76
|
.map(t => t.target)
|
|
77
|
-
.filter(id =>
|
|
77
|
+
.filter(id => idx.typeOf(id) === 'FUNC');
|
|
78
78
|
for (const fid of funcIds) {
|
|
79
|
-
const nestedCompose =
|
|
80
|
-
graph.elements.some(e => e.id === t.target && e.type === 'FUNC'));
|
|
79
|
+
const nestedCompose = idx.out(fid, 'compose').filter(t => idx.typeOf(t.target) === 'FUNC');
|
|
81
80
|
if (nestedCompose.length > 0) {
|
|
82
81
|
violations.push({
|
|
83
82
|
rule_id: 'FC-03',
|
|
@@ -85,7 +84,7 @@ export function fc03FchainFlat(graph) {
|
|
|
85
84
|
element_id: fid,
|
|
86
85
|
message: `${fid} in ${fc.id} has nested FUNC compose (should be flat)`,
|
|
87
86
|
fix_hint: 'Move nested functions to FCHAIN level (flat composition)',
|
|
88
|
-
context: { element_type: 'FUNC', element_name:
|
|
87
|
+
context: { element_type: 'FUNC', element_name: idx.byId.get(fid)?.name ?? fid },
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
90
|
}
|
|
@@ -105,24 +104,29 @@ export function fc03FchainFlat(graph) {
|
|
|
105
104
|
// to have unrelated ACTOR io.
|
|
106
105
|
// ---------------------------------------------------------------------------
|
|
107
106
|
export function fc04ActorBounded(graph) {
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// CR-SM-264: die beiden Zwischenmengen einmal je Graph statt |io|^2 je Kette.
|
|
108
|
+
// `actorFed` sind die Knoten, in die ein ACTOR hineinschickt, `actorConsumed` die, aus denen
|
|
109
|
+
// ein ACTOR liest. Beide Richtungen bleiben getrennt — anders als bei FC-01, das nur fragt,
|
|
110
|
+
// OB ein ACTOR anliegt; FC-04 verlangt Eingang UND Ausgang.
|
|
111
|
+
const idx = indexOf(graph);
|
|
112
|
+
const io = idx.tracesOfType('io');
|
|
113
|
+
const actorFed = new Set();
|
|
114
|
+
const actorConsumed = new Set();
|
|
115
|
+
for (const a of io) {
|
|
116
|
+
if (idx.typeOf(a.source) === 'ACTOR')
|
|
117
|
+
actorFed.add(a.target);
|
|
118
|
+
if (idx.typeOf(a.target) === 'ACTOR')
|
|
119
|
+
actorConsumed.add(a.source);
|
|
120
|
+
}
|
|
121
|
+
return idx.elementsOfType('FCHAIN')
|
|
110
122
|
.filter(fc => {
|
|
111
|
-
const funcIds = new Set(
|
|
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')));
|
|
123
|
+
const funcIds = new Set(idx.out(fc.id, 'compose').map(t => t.target).filter(id => idx.typeOf(id) === 'FUNC'));
|
|
115
124
|
if (funcIds.size === 0)
|
|
116
125
|
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
126
|
// Entry: ACTOR→FLOW→FUNC∈chain (something triggers the chain).
|
|
121
|
-
const entry = io.some(e => funcIds.has(e.target) &&
|
|
122
|
-
io.some(a => a.target === e.source && isActor(a.source)));
|
|
127
|
+
const entry = io.some(e => funcIds.has(e.target) && idx.typeOf(e.source) === 'FLOW' && actorFed.has(e.source));
|
|
123
128
|
// Exit: FUNC∈chain→FLOW→ACTOR (the chain produces something back to an actor).
|
|
124
|
-
const exit = io.some(e => funcIds.has(e.source) &&
|
|
125
|
-
io.some(a => a.source === e.target && isActor(a.target)));
|
|
129
|
+
const exit = io.some(e => funcIds.has(e.source) && idx.typeOf(e.target) === 'FLOW' && actorConsumed.has(e.target));
|
|
126
130
|
return !(entry && exit);
|
|
127
131
|
})
|
|
128
132
|
.map(fc => ({
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-258 / CR-SM-260 — eine committete `*.graph.json` ist FLACH.
|
|
3
|
+
*
|
|
4
|
+
* graphcode schreibt seine SSOT seit CR-GC-219 flach, damit die Datei, die ein Mensch diffed,
|
|
5
|
+
* jeden Wert genau einmal traegt: `{id, type, name, description, ...attributes}`, gar kein
|
|
6
|
+
* `attributes`-Schluessel. Die Regeln lesen aber `e.attributes?.*` — der Zustand, den der
|
|
7
|
+
* Live-Gate aus Kuzu projiziert. Wer die flache Form ungehoben in den Evaluator reicht, macht
|
|
8
|
+
* JEDES Attribut unsichtbar:
|
|
9
|
+
*
|
|
10
|
+
* graphcode-Selbstmodell, graphVersion 171 — flach gelesen | korrekt genestet
|
|
11
|
+
* R-19 123 | 0 R-20 95 | 0 Summe 610 | 374
|
|
12
|
+
*
|
|
13
|
+
* 236 Phantom-Befunde, und CR-R02 (severity error) war umgekehrt komplett stumm. Jede
|
|
14
|
+
* Gate-7-Zahl zwischen CR-SM-246 und CR-SM-258 war davon betroffen.
|
|
15
|
+
*
|
|
16
|
+
* Warum die Hebung hier im QUELLTEXT steht und nicht mehr in der Messdatei (CR-SM-260): sie hat
|
|
17
|
+
* einen zweiten Nutzer bekommen — den Regel-Profillauf in `@sigloch/se-engine`, der dasselbe
|
|
18
|
+
* Selbstmodell liest. Zwei Kopien waeren zwei Projektionen derselben Wahrheit, und die
|
|
19
|
+
* fehlerhafte faellt genau dann nicht auf, wenn sie nur Zeiten misst statt Befunde.
|
|
20
|
+
*
|
|
21
|
+
* Warum nicht `fromOntologyGraph` aus `@sigloch/graph-api-core` (CR-SM-254): das Paket haengt an
|
|
22
|
+
* contracts, nicht umgekehrt — ein Import waere ein Zyklus. Statt die Projektion von Hand
|
|
23
|
+
* nachzubauen (genau der Fehler, der hier repariert wird), leitet die Hebung ihre kanonische
|
|
24
|
+
* Schluesselmenge aus den Zod-Schemata ab. Sie kann damit nicht von der Ontologie wegdriften:
|
|
25
|
+
* ein neues Top-Level-Feld ist sofort auch hier eines.
|
|
26
|
+
*/
|
|
27
|
+
import { OntologyElement, Trace } from './ontology.js';
|
|
28
|
+
const TOP_LEVEL_ELEMENT_KEYS = new Set(Object.keys(OntologyElement.shape));
|
|
29
|
+
const TOP_LEVEL_TRACE_KEYS = new Set(Object.keys(Trace.shape));
|
|
30
|
+
function nestAttributes(entry, canonical) {
|
|
31
|
+
if (entry.attributes !== undefined)
|
|
32
|
+
return entry; // schon in der genesteten Form
|
|
33
|
+
const top = {};
|
|
34
|
+
const attributes = {};
|
|
35
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
36
|
+
(canonical.has(key) ? top : attributes)[key] = value;
|
|
37
|
+
}
|
|
38
|
+
// `status` lebt in BEIDEN Welten: als Top-Level-Feld am Schema und unter `attributes`, wo
|
|
39
|
+
// CR-R02/CR-R03/CR-R04 es lesen. Die Kuzu-Projektion liefert beides, also spiegelt die
|
|
40
|
+
// Hebung es ebenfalls — sonst lesen genau die CR-Regeln wieder ins Leere.
|
|
41
|
+
if (entry.status !== undefined)
|
|
42
|
+
attributes.status = entry.status;
|
|
43
|
+
return { ...top, attributes };
|
|
44
|
+
}
|
|
45
|
+
/** Hebt eine flach committete SSOT in die Form, die der Live-Gate dem Evaluator gibt. */
|
|
46
|
+
export function toEvaluableGraph(graph) {
|
|
47
|
+
return {
|
|
48
|
+
...graph,
|
|
49
|
+
elements: graph.elements.map((e) => nestAttributes(e, TOP_LEVEL_ELEMENT_KEYS)),
|
|
50
|
+
traces: graph.traces.map((t) => nestAttributes(t, TOP_LEVEL_TRACE_KEYS)),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -13,11 +13,12 @@ export declare const GRAMMAR_SNAPSHOT: {
|
|
|
13
13
|
readonly versions: {
|
|
14
14
|
readonly ontology: "7.0.0";
|
|
15
15
|
readonly metaModel: "2.0.0";
|
|
16
|
-
readonly rules: "6.
|
|
16
|
+
readonly rules: "6.3.0";
|
|
17
17
|
};
|
|
18
18
|
readonly elementTypes: readonly ["ACTOR", "CR", "FCHAIN", "FLOW", "FUNC", "MOD", "MS", "REQ", "SCHEMA", "SESSION", "SYS", "TEST", "UC"];
|
|
19
19
|
readonly traceTypes: readonly ["allocate", "compose", "io", "produces", "relation", "satisfy", "verify"];
|
|
20
20
|
readonly patterns: readonly ["ACTOR -io-> FLOW", "ACTOR -io-> UC", "CR -relation-> FUNC", "CR -relation-> MOD", "CR -relation-> MS", "CR -relation-> REQ", "CR -relation-> UC", "FCHAIN -compose-> FUNC [1..*]", "FCHAIN -satisfy-> REQ", "FLOW -io-> ACTOR", "FLOW -io-> FUNC", "FLOW -io-> UC", "FLOW -relation-> SCHEMA", "FUNC -allocate-> MOD", "FUNC -compose-> FUNC [0..*]", "FUNC -io-> FLOW", "FUNC -satisfy-> REQ", "MOD -compose-> MOD [0..*]", "MOD -io-> MOD", "MOD -satisfy-> REQ", "MS -compose-> FUNC", "MS -compose-> MS", "MS -compose-> REQ", "MS -compose-> UC", "MS -relation-> MS", "REQ -compose-> REQ [0..*]", "SESSION -produces-> * (audit)", "SYS -compose-> MOD [0..*]", "SYS -compose-> REQ [0..*]", "SYS -compose-> SYS [0..*]", "SYS -compose-> UC [1..*]", "SYS -satisfy-> REQ", "TEST -verify-> REQ", "UC -compose-> FCHAIN [1..*]", "UC -compose-> REQ [1..*]"];
|
|
21
21
|
readonly attributes: readonly ["CR.rationale: string", "CR.spike: boolean", "CR.status: enum", "FLOW.protocol: string", "FLOW.qos: string", "FUNC.concept: boolean", "FUNC.external: boolean", "FUNC.measuredMs: number", "FUNC.realRef: object", "FUNC.safety_relevant: boolean", "FUNC.sourceFile: string", "FUNC.timingBudgetMs: number", "MOD.concept: boolean", "MOD.external: boolean", "MOD.kind: string", "MOD.path: string", "MOD.realRef: object", "REQ.detection: number", "REQ.occurrence: number", "REQ.severity: number", "SCHEMA.concept: boolean", "SCHEMA.contract: string", "SCHEMA.external: boolean", "SCHEMA.realRef: object", "TEST.concept: boolean", "TEST.sourceFile: string", "TEST.testRefs: array", "UC.operatingMode: string"];
|
|
22
22
|
readonly rules: readonly ["AF-01 (warning) domain=[graph]", "AF-02 (warning) domain=[graph]", "AF-03 (warning) domain=[graph]", "AF-04 (warning) domain=[graph]", "AF-05 (warning) domain=[graph]", "AO-D01 (info) domain=[FUNC]", "AO-D03 (info) domain=[FUNC]", "BQ-01 (warning) domain=[REQ]", "BQ-02 (warning) domain=[REQ]", "BQ-04 (warning) domain=[REQ]", "BQ-06 (warning) domain=[REQ]", "BQ-07 (warning) domain=[REQ]", "CA-01 (error) domain=[FUNC]", "CL-01 (warning) domain=[ACTOR]", "CR-01 (warning) domain=[MOD]", "CR-R01 (error) domain=[CR]", "CR-R02 (error) domain=[CR]", "CR-R03 (warning) domain=[all]", "CR-R04 (warning) domain=[CR]", "FC-01 (warning) domain=[FCHAIN]", "FC-02 (warning) domain=[UC]", "FC-03 (warning) domain=[FUNC]", "FC-04 (warning) domain=[FCHAIN]", "FM-01 (warning) domain=[REQ]", "FM-02 (warning) domain=[REQ]", "FM-03 (error) domain=[REQ]", "IO-01 (warning) domain=[FUNC]", "MS-01 (warning) domain=[MS]", "MS-02 (error) domain=[MS]", "MS-03 (info) domain=[CR]", "MT-01 (warning) domain=[MOD]", "MT-02 (info) domain=[MOD]", "ND-01 (error) domain=[FUNC]", "ND-02 (error) domain=[SCHEMA]", "NFR-01 (warning) domain=[FCHAIN,FUNC,MOD]", "PH-01 (info) domain=[MOD]", "R-01 (error) domain=[REQ]", "R-02 (warning) domain=[FUNC]", "R-03 (error) domain=[MOD]", "R-04 (warning) domain=[MOD]", "R-05 (warning) domain=[TEST]", "R-08 (error) domain=[all]", "R-10 (warning) domain=[FLOW]", "R-12 (warning) domain=[FUNC]", "R-14 (warning) domain=[UC]", "R-15 (warning) domain=[FCHAIN]", "R-16 (warning) domain=[ACTOR]", "R-17 (warning) domain=[SYS]", "R-18 (error) domain=[all]", "R-19 (warning) domain=[TEST]", "R-20 (warning) domain=[FUNC]", "R-21 (warning) domain=[FCHAIN]", "R-22 (warning) domain=[FUNC]", "R-23 (warning) domain=[MOD]", "R-26 (warning) domain=[SCHEMA]", "R-27 (warning) domain=[MOD]", "R-29 (error) domain=[TEST]", "R-30 (warning) domain=[FUNC]", "R-31 (warning) domain=[FUNC]", "RD-01 (warning) domain=[REQ]", "RD-02 (warning) domain=[REQ]", "RD-03 (info) domain=[REQ]", "RD-04 (warning) domain=[FUNC,MOD,SYS]", "RT-01 (error) domain=[FUNC]", "SC-02 (warning) domain=[SCHEMA]", "SC-04 (warning) domain=[FLOW]", "UC-01 (error) domain=[UC]", "UC-02 (error) domain=[UC]", "UC-03 (warning) domain=[UC]", "UC-04 (warning) domain=[UC]", "UC-05 (info) domain=[UC]", "UC-06 (info) domain=[UC]", "VR-01 (info) domain=[TEST]"];
|
|
23
|
+
readonly conformanceRules: readonly ["RC-01 (error)", "RC-02 (error)", "RC-03 (error)", "RC-04 (warning)", "RC-05 (warning)", "RC-06 (warning)"];
|
|
23
24
|
};
|
|
@@ -13,7 +13,7 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
13
13
|
versions: {
|
|
14
14
|
ontology: "7.0.0",
|
|
15
15
|
metaModel: "2.0.0",
|
|
16
|
-
rules: "6.
|
|
16
|
+
rules: "6.3.0",
|
|
17
17
|
},
|
|
18
18
|
elementTypes: [
|
|
19
19
|
"ACTOR",
|
|
@@ -181,4 +181,12 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
181
181
|
"UC-06 (info) domain=[UC]",
|
|
182
182
|
"VR-01 (info) domain=[TEST]",
|
|
183
183
|
],
|
|
184
|
+
conformanceRules: [
|
|
185
|
+
"RC-01 (error)",
|
|
186
|
+
"RC-02 (error)",
|
|
187
|
+
"RC-03 (error)",
|
|
188
|
+
"RC-04 (warning)",
|
|
189
|
+
"RC-05 (warning)",
|
|
190
|
+
"RC-06 (warning)",
|
|
191
|
+
],
|
|
184
192
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-264 — ein Index je Graph statt Vollscans je Regel.
|
|
3
|
+
*
|
|
4
|
+
* ## Der Befund, der ihn rechtfertigt
|
|
5
|
+
*
|
|
6
|
+
* Nach CR-SM-261 ist das Profil flach: keine Regel ueber 7,2 %, praktisch jede bei n^2,0
|
|
7
|
+
* (`npm run profile:rules -w @sigloch/se-engine`). Das ist keine einzelne teure Regel mehr,
|
|
8
|
+
* sondern EINE Form, die im ganzen Katalog steht — 58 Stellen der Bauart
|
|
9
|
+
*
|
|
10
|
+
* graph.elements.some(e => e.id === t.target && e.type === 'FLOW') // O(E), in einer T-Schleife
|
|
11
|
+
* graph.traces.filter(t => t.source === x && t.type === 'compose') // O(T), je Element
|
|
12
|
+
*
|
|
13
|
+
* Jede einzelne ist `O(E)` bzw. `O(T)` und steht in einer Schleife ueber die jeweils andere
|
|
14
|
+
* Menge. Der Index ersetzt beide durch konstante Lookups.
|
|
15
|
+
*
|
|
16
|
+
* ## Warum eine WeakMap und kein dritter Parameter an `evaluate`
|
|
17
|
+
*
|
|
18
|
+
* Die Alternative waere ein dritter Parameter an `RuleDefinition.evaluate` gewesen — 73
|
|
19
|
+
* Signaturen, und jede Regelfamilie muesste in einem Zug umgestellt werden. Diese Fassung
|
|
20
|
+
* kostet keine Signatur: eine Regel ruft `indexOf(graph)` und bekommt den Index, den die erste
|
|
21
|
+
* Regel desselben Laufs gebaut hat. Damit ist die Umstellung Regel fuer Regel moeglich
|
|
22
|
+
* (CR-SM-264 ist entlang der Familien geschnitten), und eine noch nicht umgestellte Regel
|
|
23
|
+
* funktioniert unveraendert weiter.
|
|
24
|
+
*
|
|
25
|
+
* Der Schluessel ist das Graph-OBJEKT. `applyRule` (se-engine) arbeitet auf einem
|
|
26
|
+
* `structuredClone` — ein anderes Objekt, also ein anderer Index, korrekt per Konstruktion.
|
|
27
|
+
* Eine `WeakMap` gibt den Eintrag frei, sobald der Graph es tut; es entsteht kein Cache, den
|
|
28
|
+
* jemand invalidieren muesste.
|
|
29
|
+
*
|
|
30
|
+
* ## Die Invariante, auf der das ruht
|
|
31
|
+
*
|
|
32
|
+
* **Regeln lesen, sie mutieren nicht.** Wuerde eine Regel `graph.elements` oder `graph.traces`
|
|
33
|
+
* in place aendern, waere der Index danach falsch — still, und nur bei den Regeln, die nach ihr
|
|
34
|
+
* laufen. `se-graph-index.test.ts` haelt das fest.
|
|
35
|
+
*/
|
|
36
|
+
import type { OntologyElement, OntologyGraph, Trace } from './ontology.js';
|
|
37
|
+
export interface GraphIndex {
|
|
38
|
+
/** Element je id — ersetzt `elements.find(e => e.id === x)`. */
|
|
39
|
+
byId: ReadonlyMap<string, OntologyElement>;
|
|
40
|
+
/** Typ je id — ersetzt `elements.some(e => e.id === x && e.type === 'Y')`. */
|
|
41
|
+
typeOf(id: string): OntologyElement['type'] | undefined;
|
|
42
|
+
/** Elemente eines Typs, in GRAPH-Reihenfolge — ersetzt `elements.filter(e => e.type === 'X')`. */
|
|
43
|
+
elementsOfType(type: OntologyElement['type']): readonly OntologyElement[];
|
|
44
|
+
/** ids eines Typs, als Menge. */
|
|
45
|
+
idsOfType(type: OntologyElement['type']): ReadonlySet<string>;
|
|
46
|
+
/** Ausgehende Traces je (source, type), in Graph-Reihenfolge. */
|
|
47
|
+
out(source: string, type: Trace['type']): readonly Trace[];
|
|
48
|
+
/** Eingehende Traces je (target, type), in Graph-Reihenfolge. */
|
|
49
|
+
in(target: string, type: Trace['type']): readonly Trace[];
|
|
50
|
+
/** Alle Traces eines Typs, in Graph-Reihenfolge. */
|
|
51
|
+
tracesOfType(type: Trace['type']): readonly Trace[];
|
|
52
|
+
/** Alle ausgehenden Traces eines Elements, TYPUNABHAENGIG, in Graph-Reihenfolge. */
|
|
53
|
+
outAll(source: string): readonly Trace[];
|
|
54
|
+
}
|
|
55
|
+
/** Der Index dieses Graphen — einmal gebaut, danach konstante Lookups. */
|
|
56
|
+
export declare function indexOf(graph: OntologyGraph): GraphIndex;
|