@principal-ai/subsystems-core 0.41.0 → 0.43.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/types/subsystem-model.d.ts +9 -29
- package/dist/types/subsystem-model.d.ts.map +1 -1
- package/dist/types/subsystem-model.js +4 -19
- package/dist/types/subsystem-model.js.map +1 -1
- package/dist/validation.d.ts +2 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +29 -14
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/schemas/subsystem-model.schema.json +6 -61
- package/src/types/subsystem-model.test.ts +0 -2
- package/src/types/subsystem-model.ts +10 -57
- package/src/validation.test.ts +73 -13
- package/src/validation.ts +31 -16
|
@@ -43,21 +43,16 @@ export type SubsystemFramework = string;
|
|
|
43
43
|
* Empty when no framework pattern applies. Pair with `framework` when set.
|
|
44
44
|
*/
|
|
45
45
|
export type SubsystemStereotype = string;
|
|
46
|
-
/**
|
|
47
|
-
* Topology relation type — structural / module / type claims between
|
|
48
|
-
* components. Belongs on `relations[]`, not on walkthrough hops.
|
|
49
|
-
*/
|
|
50
|
-
export type SubsystemRelationType = 'extends' | 'inherits' | 'implements' | 'mixes_in' | 'method';
|
|
51
46
|
/**
|
|
52
47
|
* Walkthrough hop mechanism — runtime seams with a `file:line` site.
|
|
53
48
|
* Belongs on walkthrough steps; graph edges for these are derived.
|
|
54
49
|
*/
|
|
55
50
|
export type SubsystemWalkthroughMechanism = 'calls' | 'uses' | 'feeds' | 'produces' | 'writes' | 'reads' | 'watches' | 'registers-into';
|
|
56
51
|
/**
|
|
57
|
-
*
|
|
58
|
-
* walkthrough
|
|
52
|
+
* Edge mechanism used by derived display edges / styling. Display edges are
|
|
53
|
+
* derived from walkthrough hops, so this is the walkthrough mechanism.
|
|
59
54
|
*/
|
|
60
|
-
export type SubsystemEdgeMechanism =
|
|
55
|
+
export type SubsystemEdgeMechanism = SubsystemWalkthroughMechanism;
|
|
61
56
|
export type SubsystemDeclarationProvenance = 'verified' | 'authored';
|
|
62
57
|
export type SubsystemDeclTokenKind = 'keyword' | 'name' | 'member' | 'type' | 'punctuation' | 'string' | 'newline';
|
|
63
58
|
export interface SubsystemDeclToken {
|
|
@@ -212,7 +207,7 @@ export type SubsystemConstructDeclaration = SubsystemClassDeclaration | Subsyste
|
|
|
212
207
|
/** A component node — the named unit, construct-tagged. */
|
|
213
208
|
export interface SubsystemComponent {
|
|
214
209
|
/**
|
|
215
|
-
* Model-local stable alias. Referenced by
|
|
210
|
+
* Model-local stable alias. Referenced by walkthrough `from` /
|
|
216
211
|
* `to`; unique per model. Edges point at the alias, not the location — a
|
|
217
212
|
* file move or symbol rename leaves edges intact. Code identity lives on
|
|
218
213
|
* `purl` + `file` + `symbol` and is what composed (multi-model) views
|
|
@@ -275,27 +270,15 @@ export interface SubsystemComponent {
|
|
|
275
270
|
/** Location anchor (file/line/hash) — distinct from `declaration` (shape). */
|
|
276
271
|
declarationRef?: SubsystemDeclarationRef;
|
|
277
272
|
}
|
|
278
|
-
/** A topology relation between components (structural / module / type). */
|
|
279
|
-
export interface SubsystemRelation {
|
|
280
|
-
id: string;
|
|
281
|
-
/** Source component alias. */
|
|
282
|
-
from: string;
|
|
283
|
-
/** Target component alias (or external label). */
|
|
284
|
-
to: string;
|
|
285
|
-
relationType: SubsystemRelationType;
|
|
286
|
-
/** Concrete file/symbol evidence (often purls). */
|
|
287
|
-
refs?: string[];
|
|
288
|
-
}
|
|
289
273
|
/**
|
|
290
|
-
* Derived / display graph edge used by renderers. Built from
|
|
291
|
-
*
|
|
274
|
+
* Derived / display graph edge used by renderers. Built from walkthrough
|
|
275
|
+
* hops — not authored as its own document field.
|
|
292
276
|
*/
|
|
293
277
|
export interface SubsystemComponentEdge {
|
|
294
278
|
id: string;
|
|
295
279
|
from: string;
|
|
296
280
|
to: string;
|
|
297
281
|
mechanism: SubsystemEdgeMechanism;
|
|
298
|
-
refs?: string[];
|
|
299
282
|
}
|
|
300
283
|
export interface SubsystemWalkthroughStep {
|
|
301
284
|
/** Source component alias. */
|
|
@@ -353,8 +336,6 @@ export interface SubsystemModelDocument {
|
|
|
353
336
|
title: string;
|
|
354
337
|
description?: string;
|
|
355
338
|
components: SubsystemComponent[];
|
|
356
|
-
/** Topology relations (structural / module / type). May be empty. */
|
|
357
|
-
relations: SubsystemRelation[];
|
|
358
339
|
/** Runtime walkthroughs (ordered hops with sites). */
|
|
359
340
|
walkthroughs?: SubsystemWalkthrough[];
|
|
360
341
|
/**
|
|
@@ -395,14 +376,13 @@ export declare function isSubsystemModelDocument(value: unknown): value is Subsy
|
|
|
395
376
|
* share surface that must stay schema-clean.
|
|
396
377
|
*/
|
|
397
378
|
export declare function toPortableDocument(doc: SubsystemModelDocument): SubsystemModelDocument;
|
|
398
|
-
/** Stable id for a derived graph edge from a
|
|
379
|
+
/** Stable id for a derived graph edge from a walkthrough hop. */
|
|
399
380
|
export declare function derivedGraphEdgeId(from: string, to: string, mechanism: SubsystemEdgeMechanism): string;
|
|
400
381
|
/**
|
|
401
|
-
* Build display edges for the graph canvas from
|
|
402
|
-
*
|
|
382
|
+
* Build display edges for the graph canvas from walkthrough hops
|
|
383
|
+
* (deduped by from/to/mechanism).
|
|
403
384
|
*/
|
|
404
385
|
export declare function deriveGraphEdges(doc: {
|
|
405
|
-
relations?: SubsystemRelation[];
|
|
406
386
|
walkthroughs?: SubsystemWalkthrough[];
|
|
407
387
|
}): SubsystemComponentEdge[];
|
|
408
388
|
//# sourceMappingURL=subsystem-model.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subsystem-model.d.ts","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;0CAE0C;AAC1C,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP,UAAU,GACV,QAAQ,GACR,WAAW,GACX,YAAY,GACZ,MAAM,GACN,OAAO,GACP,UAAU,GACV,eAAe,CAAC;AAEpB,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAExC;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"subsystem-model.d.ts","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,9 +31,7 @@ function isSubsystemModelDocument(value) {
|
|
|
31
31
|
if (!value || typeof value !== 'object')
|
|
32
32
|
return false;
|
|
33
33
|
const v = value;
|
|
34
|
-
return
|
|
35
|
-
Array.isArray(v.components) &&
|
|
36
|
-
Array.isArray(v.relations));
|
|
34
|
+
return typeof v.title === 'string' && Array.isArray(v.components);
|
|
37
35
|
}
|
|
38
36
|
exports.isSubsystemModelDocument = isSubsystemModelDocument;
|
|
39
37
|
/**
|
|
@@ -45,7 +43,6 @@ function toPortableDocument(doc) {
|
|
|
45
43
|
const out = {
|
|
46
44
|
title: doc.title,
|
|
47
45
|
components: doc.components,
|
|
48
|
-
relations: doc.relations,
|
|
49
46
|
};
|
|
50
47
|
if (doc.$schema)
|
|
51
48
|
out.$schema = doc.$schema;
|
|
@@ -60,29 +57,17 @@ function toPortableDocument(doc) {
|
|
|
60
57
|
return out;
|
|
61
58
|
}
|
|
62
59
|
exports.toPortableDocument = toPortableDocument;
|
|
63
|
-
/** Stable id for a derived graph edge from a
|
|
60
|
+
/** Stable id for a derived graph edge from a walkthrough hop. */
|
|
64
61
|
function derivedGraphEdgeId(from, to, mechanism) {
|
|
65
62
|
return `${from}--${mechanism}-->${to}`;
|
|
66
63
|
}
|
|
67
64
|
exports.derivedGraphEdgeId = derivedGraphEdgeId;
|
|
68
65
|
/**
|
|
69
|
-
* Build display edges for the graph canvas from
|
|
70
|
-
*
|
|
66
|
+
* Build display edges for the graph canvas from walkthrough hops
|
|
67
|
+
* (deduped by from/to/mechanism).
|
|
71
68
|
*/
|
|
72
69
|
function deriveGraphEdges(doc) {
|
|
73
70
|
const byId = new Map();
|
|
74
|
-
for (const r of doc.relations ?? []) {
|
|
75
|
-
const id = r.id || derivedGraphEdgeId(r.from, r.to, r.relationType);
|
|
76
|
-
if (!byId.has(id)) {
|
|
77
|
-
byId.set(id, {
|
|
78
|
-
id,
|
|
79
|
-
from: r.from,
|
|
80
|
-
to: r.to,
|
|
81
|
-
mechanism: r.relationType,
|
|
82
|
-
refs: r.refs,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
71
|
for (const w of doc.walkthroughs ?? []) {
|
|
87
72
|
for (const step of w.steps) {
|
|
88
73
|
const id = derivedGraphEdgeId(step.from, step.to, step.mechanism);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subsystem-model.js","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;
|
|
1
|
+
{"version":3,"file":"subsystem-model.js","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AA8ZH;;;;GAIG;AACH,SAAgB,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,KAGT,CAAC;IACF,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AACpE,CAAC;AAPD,4DAOC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAChC,GAA2B;IAE3B,MAAM,GAAG,GAA2B;QAClC,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,UAAU,EAAE,GAAG,CAAC,UAAU;KAC3B,CAAC;IACF,IAAI,GAAG,CAAC,OAAO;QAAE,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAC3C,IAAI,GAAG,CAAC,WAAW;QAAE,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;IACvD,IAAI,GAAG,CAAC,YAAY;QAAE,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC;IAC1D,IAAI,GAAG,CAAC,gBAAgB;QAAE,GAAG,CAAC,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,CAAC;IACtE,IAAI,GAAG,CAAC,iBAAiB;QAAE,GAAG,CAAC,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,CAAC;IACzE,OAAO,GAAG,CAAC;AACb,CAAC;AAbD,gDAaC;AAED,iEAAiE;AACjE,SAAgB,kBAAkB,CAChC,IAAY,EACZ,EAAU,EACV,SAAiC;IAEjC,OAAO,GAAG,IAAI,KAAK,SAAS,MAAM,EAAE,EAAE,CAAC;AACzC,CAAC;AAND,gDAMC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,GAEhC;IACC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkC,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,YAAY,IAAI,EAAE,EAAE;QACtC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE;YAC1B,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAClE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;gBACjB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE;oBACX,EAAE;oBACF,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,SAAS,EAAE,IAAI,CAAC,SAAS;iBAC1B,CAAC,CAAC;aACJ;SACF;KACF;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5B,CAAC;AAlBD,4CAkBC"}
|
package/dist/validation.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These are the rules the JSON Schema (`schemas/subsystem-model.schema.json`)
|
|
5
5
|
* cannot express — anything that spans fields or arrays: alias uniqueness,
|
|
6
|
-
* referential integrity between
|
|
6
|
+
* referential integrity between walkthroughs and components, and the
|
|
7
7
|
* `module` implies `file` invariant. Structural checks (types, `required`,
|
|
8
8
|
* enums, ranges, closed objects) belong to the schema and are enforced per
|
|
9
9
|
* surface; this module owns only what the schema can't.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { SubsystemModelDocument } from './types/subsystem-model';
|
|
14
14
|
export interface SubsystemValidationProblem {
|
|
15
|
-
/** JSON-pointer-ish location, e.g. `/components/2` or `/
|
|
15
|
+
/** JSON-pointer-ish location, e.g. `/components/2` or `/walkthroughs/0/steps/0/from`. */
|
|
16
16
|
path: string;
|
|
17
17
|
message: string;
|
|
18
18
|
}
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,0BAA0B;IACzC,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,0BAA0B;IACzC,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAyBD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,sBAAsB,GAC1B,0BAA0B,EAAE,CAuE9B"}
|
package/dist/validation.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* These are the rules the JSON Schema (`schemas/subsystem-model.schema.json`)
|
|
6
6
|
* cannot express — anything that spans fields or arrays: alias uniqueness,
|
|
7
|
-
* referential integrity between
|
|
7
|
+
* referential integrity between walkthroughs and components, and the
|
|
8
8
|
* `module` implies `file` invariant. Structural checks (types, `required`,
|
|
9
9
|
* enums, ranges, closed objects) belong to the schema and are enforced per
|
|
10
10
|
* surface; this module owns only what the schema can't.
|
|
@@ -16,6 +16,24 @@ exports.validateSubsystemModelCrossField = void 0;
|
|
|
16
16
|
function isUngrounded(c) {
|
|
17
17
|
return c.construct === 'external' || c.proposed === true;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* `file` is a path *inside the repo named by `purl`* — it resolves against that
|
|
21
|
+
* checkout, never against an installed artifact. `node_modules/` is installed,
|
|
22
|
+
* gitignored, and its layout depends on hoisting, so a claim anchored there can
|
|
23
|
+
* never resolve and is unverifiable by construction.
|
|
24
|
+
*
|
|
25
|
+
* Such a component is a third-party dependency: model it as `construct:
|
|
26
|
+
* 'external'` with `purl: 'pkg:npm/<package>'` and no file. Applies to externals
|
|
27
|
+
* too — they carry no file by design, so an install path there is dead weight
|
|
28
|
+
* that draws a link nothing can open. `proposed` is exempt like every other
|
|
29
|
+
* grounding rule: its file is a placeholder for something not placed yet.
|
|
30
|
+
*
|
|
31
|
+
* The same rule covers walkthrough step files — a seam into an external belongs
|
|
32
|
+
* at the call site in the caller, which *is* in the repo.
|
|
33
|
+
*/
|
|
34
|
+
function mentionsNodeModules(path) {
|
|
35
|
+
return /(^|\/)node_modules(\/|$)/.test(path);
|
|
36
|
+
}
|
|
19
37
|
/**
|
|
20
38
|
* Validate the cross-field rules of a subsystem model document. Returns an
|
|
21
39
|
* empty array when the document is consistent.
|
|
@@ -23,10 +41,9 @@ function isUngrounded(c) {
|
|
|
23
41
|
function validateSubsystemModelCrossField(doc) {
|
|
24
42
|
const problems = [];
|
|
25
43
|
const components = doc.components ?? [];
|
|
26
|
-
const relations = doc.relations ?? [];
|
|
27
44
|
const walkthroughs = doc.walkthroughs ?? [];
|
|
28
45
|
// Component aliases must be unique, and the set is the referential target
|
|
29
|
-
// for
|
|
46
|
+
// for walkthrough steps.
|
|
30
47
|
const ids = new Set();
|
|
31
48
|
components.forEach((c, i) => {
|
|
32
49
|
if (ids.has(c.alias)) {
|
|
@@ -48,18 +65,10 @@ function validateSubsystemModelCrossField(doc) {
|
|
|
48
65
|
message: `component ${JSON.stringify(c.alias)}: module ${JSON.stringify(module)} is set but file is empty — a module frame needs a file to ground it (mark the component proposed if it is not placed yet).`,
|
|
49
66
|
});
|
|
50
67
|
}
|
|
51
|
-
|
|
52
|
-
relations.forEach((r, i) => {
|
|
53
|
-
if (!ids.has(r.from)) {
|
|
54
|
-
problems.push({
|
|
55
|
-
path: `/relations/${i}/from`,
|
|
56
|
-
message: `relation ${JSON.stringify(r.id)}: from ${JSON.stringify(r.from)} does not match any component alias`,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (!ids.has(r.to)) {
|
|
68
|
+
if (file && c.proposed !== true && mentionsNodeModules(file)) {
|
|
60
69
|
problems.push({
|
|
61
|
-
path: `/
|
|
62
|
-
message: `
|
|
70
|
+
path: `/components/${i}/file`,
|
|
71
|
+
message: `component ${JSON.stringify(c.alias)}: file ${JSON.stringify(file)} points into node_modules — installed artifacts are not part of the repo and cannot be verified. Model the dependency as construct "external" with purl "pkg:npm/<package>" and no file, or anchor the claim to the package's real source.`,
|
|
63
72
|
});
|
|
64
73
|
}
|
|
65
74
|
});
|
|
@@ -89,6 +98,12 @@ function validateSubsystemModelCrossField(doc) {
|
|
|
89
98
|
});
|
|
90
99
|
}
|
|
91
100
|
}
|
|
101
|
+
if (mentionsNodeModules(step.file)) {
|
|
102
|
+
problems.push({
|
|
103
|
+
path: `/walkthroughs/${wi}/steps/${si}/file`,
|
|
104
|
+
message: `walkthrough ${JSON.stringify(w.id)}: step ${si} file ${JSON.stringify(step.file)} points into node_modules — anchor the seam at the call site inside the repo instead.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
92
107
|
});
|
|
93
108
|
});
|
|
94
109
|
return problems;
|
package/dist/validation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAcH,SAAS,YAAY,CAAC,CAAqB;IACzC,OAAO,CAAC,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,SAAgB,gCAAgC,CAC9C,GAA2B;IAE3B,MAAM,QAAQ,GAAiC,EAAE,CAAC;IAClD,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;IACxC,MAAM,
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://principal-ai.dev/schemas/subsystem-model.schema.json",
|
|
4
4
|
"title": "Subsystem Model",
|
|
5
|
-
"description": "Portable subsystem model — the shareable standard. Construct-tagged components (nodes) plus
|
|
5
|
+
"description": "Portable subsystem model — the shareable standard. Construct-tagged components (nodes) plus runtime walkthroughs describing one subsystem of a codebase. Ontology: construct = what a node is, framework + stereotype = which framework pattern it plays, role = where it sits, process = where it runs, module = which source file/module the export belongs to. Symbol is the code identity; name is the display label (often derived from symbol). Host-only fields (local path binding, provenance, store ids, verification) are NOT part of this document; they belong on a hydrated envelope around it.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"title",
|
|
9
|
-
"components"
|
|
10
|
-
"relations"
|
|
9
|
+
"components"
|
|
11
10
|
],
|
|
12
11
|
"additionalProperties": false,
|
|
13
12
|
"properties": {
|
|
@@ -31,13 +30,6 @@
|
|
|
31
30
|
"$ref": "#/$defs/component"
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
|
-
"relations": {
|
|
35
|
-
"type": "array",
|
|
36
|
-
"description": "Topology relations (structural / module / type). `from` / `to` reference component `alias`es. Runtime seams belong on walkthrough steps, not here.",
|
|
37
|
-
"items": {
|
|
38
|
-
"$ref": "#/$defs/relation"
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
33
|
"walkthroughs": {
|
|
42
34
|
"type": "array",
|
|
43
35
|
"description": "Ordered runtime walkthroughs (one per named behavior). Each step names from/to/mechanism and the concrete file:line where that seam fires.",
|
|
@@ -106,17 +98,6 @@
|
|
|
106
98
|
"authored"
|
|
107
99
|
]
|
|
108
100
|
},
|
|
109
|
-
"relationType": {
|
|
110
|
-
"type": "string",
|
|
111
|
-
"description": "Topology relation type — structural / module / type claim between components.",
|
|
112
|
-
"enum": [
|
|
113
|
-
"extends",
|
|
114
|
-
"inherits",
|
|
115
|
-
"implements",
|
|
116
|
-
"mixes_in",
|
|
117
|
-
"method"
|
|
118
|
-
]
|
|
119
|
-
},
|
|
120
101
|
"mechanism": {
|
|
121
102
|
"type": "string",
|
|
122
103
|
"description": "Walkthrough hop mechanism — how `from` relates to `to` at a runtime site. `uses` = general dependency; `feeds` = data-flow into a processor; `produces` = emits an output; `writes`/`reads`/`watches` = retained-state interactions.",
|
|
@@ -145,7 +126,7 @@
|
|
|
145
126
|
"alias": {
|
|
146
127
|
"type": "string",
|
|
147
128
|
"minLength": 1,
|
|
148
|
-
"description": "Model-local stable alias, unique per model. Referenced by
|
|
129
|
+
"description": "Model-local stable alias, unique per model. Referenced by walkthrough `from` / `to`; edges point at the alias, not the location, so a file move or symbol rename leaves edges intact. Code identity (for composed multi-model views) lives on `purl` + `file` + `symbol`, not here."
|
|
149
130
|
},
|
|
150
131
|
"name": {
|
|
151
132
|
"type": "string",
|
|
@@ -157,7 +138,8 @@
|
|
|
157
138
|
},
|
|
158
139
|
"file": {
|
|
159
140
|
"type": "string",
|
|
160
|
-
"
|
|
141
|
+
"pattern": "^(?!.*(^|/)node_modules(/|$)).*$",
|
|
142
|
+
"description": "Repo-root-relative path of the source location this component lives in. Resolved against the checkout of the repo named by this component's `purl` (via the Alexandria registry). Empty string allowed for pure externals. `node_modules/` is rejected: installed artifacts are not part of the repo — model a third-party dependency as `construct: external` with `purl: pkg:npm/<package>` and no file."
|
|
161
143
|
},
|
|
162
144
|
"purl": {
|
|
163
145
|
"type": "string",
|
|
@@ -224,44 +206,6 @@
|
|
|
224
206
|
}
|
|
225
207
|
}
|
|
226
208
|
},
|
|
227
|
-
"relation": {
|
|
228
|
-
"type": "object",
|
|
229
|
-
"required": [
|
|
230
|
-
"id",
|
|
231
|
-
"from",
|
|
232
|
-
"to",
|
|
233
|
-
"relationType"
|
|
234
|
-
],
|
|
235
|
-
"additionalProperties": false,
|
|
236
|
-
"properties": {
|
|
237
|
-
"id": {
|
|
238
|
-
"type": "string",
|
|
239
|
-
"minLength": 1,
|
|
240
|
-
"description": "Stable unique relation id."
|
|
241
|
-
},
|
|
242
|
-
"from": {
|
|
243
|
-
"type": "string",
|
|
244
|
-
"minLength": 1,
|
|
245
|
-
"description": "Source component `alias`."
|
|
246
|
-
},
|
|
247
|
-
"to": {
|
|
248
|
-
"type": "string",
|
|
249
|
-
"minLength": 1,
|
|
250
|
-
"description": "Target component `alias` (or external target label)."
|
|
251
|
-
},
|
|
252
|
-
"relationType": {
|
|
253
|
-
"$ref": "#/$defs/relationType"
|
|
254
|
-
},
|
|
255
|
-
"refs": {
|
|
256
|
-
"type": "array",
|
|
257
|
-
"description": "Concrete file/symbol evidence backing the relation (often purl refs).",
|
|
258
|
-
"items": {
|
|
259
|
-
"type": "string",
|
|
260
|
-
"minLength": 1
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
209
|
"walkthroughStep": {
|
|
266
210
|
"type": "object",
|
|
267
211
|
"required": [
|
|
@@ -291,6 +235,7 @@
|
|
|
291
235
|
"file": {
|
|
292
236
|
"type": "string",
|
|
293
237
|
"minLength": 1,
|
|
238
|
+
"pattern": "^(?!.*(^|/)node_modules(/|$)).*$",
|
|
294
239
|
"description": "Repo-root-relative path where the seam fires for this walkthrough."
|
|
295
240
|
},
|
|
296
241
|
"line": {
|
|
@@ -18,7 +18,6 @@ describe("toPortableDocument", () => {
|
|
|
18
18
|
const portable = toPortableDocument({
|
|
19
19
|
title: "t",
|
|
20
20
|
components: [],
|
|
21
|
-
relations: [],
|
|
22
21
|
createdAtCommits: { "pkg:github/a/b": "abc" },
|
|
23
22
|
verifiedAtCommits: { "pkg:github/a/b": "def" },
|
|
24
23
|
} as unknown as Parameters<typeof toPortableDocument>[0]);
|
|
@@ -30,7 +29,6 @@ describe("toPortableDocument", () => {
|
|
|
30
29
|
const portable = toPortableDocument({
|
|
31
30
|
title: "t",
|
|
32
31
|
components: [],
|
|
33
|
-
relations: [],
|
|
34
32
|
id: "sg-1",
|
|
35
33
|
createdAt: "now",
|
|
36
34
|
verification: {},
|
|
@@ -58,17 +58,6 @@ export type SubsystemFramework = string;
|
|
|
58
58
|
*/
|
|
59
59
|
export type SubsystemStereotype = string;
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Topology relation type — structural / module / type claims between
|
|
63
|
-
* components. Belongs on `relations[]`, not on walkthrough hops.
|
|
64
|
-
*/
|
|
65
|
-
export type SubsystemRelationType =
|
|
66
|
-
| 'extends'
|
|
67
|
-
| 'inherits'
|
|
68
|
-
| 'implements'
|
|
69
|
-
| 'mixes_in'
|
|
70
|
-
| 'method';
|
|
71
|
-
|
|
72
61
|
/**
|
|
73
62
|
* Walkthrough hop mechanism — runtime seams with a `file:line` site.
|
|
74
63
|
* Belongs on walkthrough steps; graph edges for these are derived.
|
|
@@ -84,12 +73,10 @@ export type SubsystemWalkthroughMechanism =
|
|
|
84
73
|
| 'registers-into';
|
|
85
74
|
|
|
86
75
|
/**
|
|
87
|
-
*
|
|
88
|
-
* walkthrough
|
|
76
|
+
* Edge mechanism used by derived display edges / styling. Display edges are
|
|
77
|
+
* derived from walkthrough hops, so this is the walkthrough mechanism.
|
|
89
78
|
*/
|
|
90
|
-
export type SubsystemEdgeMechanism =
|
|
91
|
-
| SubsystemRelationType
|
|
92
|
-
| SubsystemWalkthroughMechanism;
|
|
79
|
+
export type SubsystemEdgeMechanism = SubsystemWalkthroughMechanism;
|
|
93
80
|
|
|
94
81
|
export type SubsystemDeclarationProvenance = 'verified' | 'authored';
|
|
95
82
|
|
|
@@ -280,7 +267,7 @@ export type SubsystemConstructDeclaration =
|
|
|
280
267
|
/** A component node — the named unit, construct-tagged. */
|
|
281
268
|
export interface SubsystemComponent {
|
|
282
269
|
/**
|
|
283
|
-
* Model-local stable alias. Referenced by
|
|
270
|
+
* Model-local stable alias. Referenced by walkthrough `from` /
|
|
284
271
|
* `to`; unique per model. Edges point at the alias, not the location — a
|
|
285
272
|
* file move or symbol rename leaves edges intact. Code identity lives on
|
|
286
273
|
* `purl` + `file` + `symbol` and is what composed (multi-model) views
|
|
@@ -344,28 +331,15 @@ export interface SubsystemComponent {
|
|
|
344
331
|
declarationRef?: SubsystemDeclarationRef;
|
|
345
332
|
}
|
|
346
333
|
|
|
347
|
-
/** A topology relation between components (structural / module / type). */
|
|
348
|
-
export interface SubsystemRelation {
|
|
349
|
-
id: string;
|
|
350
|
-
/** Source component alias. */
|
|
351
|
-
from: string;
|
|
352
|
-
/** Target component alias (or external label). */
|
|
353
|
-
to: string;
|
|
354
|
-
relationType: SubsystemRelationType;
|
|
355
|
-
/** Concrete file/symbol evidence (often purls). */
|
|
356
|
-
refs?: string[];
|
|
357
|
-
}
|
|
358
|
-
|
|
359
334
|
/**
|
|
360
|
-
* Derived / display graph edge used by renderers. Built from
|
|
361
|
-
*
|
|
335
|
+
* Derived / display graph edge used by renderers. Built from walkthrough
|
|
336
|
+
* hops — not authored as its own document field.
|
|
362
337
|
*/
|
|
363
338
|
export interface SubsystemComponentEdge {
|
|
364
339
|
id: string;
|
|
365
340
|
from: string;
|
|
366
341
|
to: string;
|
|
367
342
|
mechanism: SubsystemEdgeMechanism;
|
|
368
|
-
refs?: string[];
|
|
369
343
|
}
|
|
370
344
|
|
|
371
345
|
export interface SubsystemWalkthroughStep {
|
|
@@ -428,8 +402,6 @@ export interface SubsystemModelDocument {
|
|
|
428
402
|
title: string;
|
|
429
403
|
description?: string;
|
|
430
404
|
components: SubsystemComponent[];
|
|
431
|
-
/** Topology relations (structural / module / type). May be empty. */
|
|
432
|
-
relations: SubsystemRelation[];
|
|
433
405
|
/** Runtime walkthroughs (ordered hops with sites). */
|
|
434
406
|
walkthroughs?: SubsystemWalkthrough[];
|
|
435
407
|
/**
|
|
@@ -470,13 +442,8 @@ export function isSubsystemModelDocument(value: unknown): value is SubsystemMode
|
|
|
470
442
|
const v = value as {
|
|
471
443
|
title?: unknown;
|
|
472
444
|
components?: unknown;
|
|
473
|
-
relations?: unknown;
|
|
474
445
|
};
|
|
475
|
-
return (
|
|
476
|
-
typeof v.title === 'string' &&
|
|
477
|
-
Array.isArray(v.components) &&
|
|
478
|
-
Array.isArray(v.relations)
|
|
479
|
-
);
|
|
446
|
+
return typeof v.title === 'string' && Array.isArray(v.components);
|
|
480
447
|
}
|
|
481
448
|
|
|
482
449
|
/**
|
|
@@ -490,7 +457,6 @@ export function toPortableDocument(
|
|
|
490
457
|
const out: SubsystemModelDocument = {
|
|
491
458
|
title: doc.title,
|
|
492
459
|
components: doc.components,
|
|
493
|
-
relations: doc.relations,
|
|
494
460
|
};
|
|
495
461
|
if (doc.$schema) out.$schema = doc.$schema;
|
|
496
462
|
if (doc.description) out.description = doc.description;
|
|
@@ -500,7 +466,7 @@ export function toPortableDocument(
|
|
|
500
466
|
return out;
|
|
501
467
|
}
|
|
502
468
|
|
|
503
|
-
/** Stable id for a derived graph edge from a
|
|
469
|
+
/** Stable id for a derived graph edge from a walkthrough hop. */
|
|
504
470
|
export function derivedGraphEdgeId(
|
|
505
471
|
from: string,
|
|
506
472
|
to: string,
|
|
@@ -510,26 +476,13 @@ export function derivedGraphEdgeId(
|
|
|
510
476
|
}
|
|
511
477
|
|
|
512
478
|
/**
|
|
513
|
-
* Build display edges for the graph canvas from
|
|
514
|
-
*
|
|
479
|
+
* Build display edges for the graph canvas from walkthrough hops
|
|
480
|
+
* (deduped by from/to/mechanism).
|
|
515
481
|
*/
|
|
516
482
|
export function deriveGraphEdges(doc: {
|
|
517
|
-
relations?: SubsystemRelation[];
|
|
518
483
|
walkthroughs?: SubsystemWalkthrough[];
|
|
519
484
|
}): SubsystemComponentEdge[] {
|
|
520
485
|
const byId = new Map<string, SubsystemComponentEdge>();
|
|
521
|
-
for (const r of doc.relations ?? []) {
|
|
522
|
-
const id = r.id || derivedGraphEdgeId(r.from, r.to, r.relationType);
|
|
523
|
-
if (!byId.has(id)) {
|
|
524
|
-
byId.set(id, {
|
|
525
|
-
id,
|
|
526
|
-
from: r.from,
|
|
527
|
-
to: r.to,
|
|
528
|
-
mechanism: r.relationType,
|
|
529
|
-
refs: r.refs,
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
486
|
for (const w of doc.walkthroughs ?? []) {
|
|
534
487
|
for (const step of w.steps) {
|
|
535
488
|
const id = derivedGraphEdgeId(step.from, step.to, step.mechanism);
|
package/src/validation.test.ts
CHANGED
|
@@ -20,7 +20,6 @@ function doc(partial: Partial<SubsystemModelDocument>): SubsystemModelDocument {
|
|
|
20
20
|
return {
|
|
21
21
|
title: 't',
|
|
22
22
|
components: [],
|
|
23
|
-
relations: [],
|
|
24
23
|
...partial,
|
|
25
24
|
} as SubsystemModelDocument;
|
|
26
25
|
}
|
|
@@ -29,7 +28,6 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
29
28
|
test('accepts a consistent document', () => {
|
|
30
29
|
const d = doc({
|
|
31
30
|
components: [comp('a'), comp('b')],
|
|
32
|
-
relations: [{ id: 'r1', from: 'a', to: 'b', relationType: 'method' }],
|
|
33
31
|
walkthroughs: [
|
|
34
32
|
{
|
|
35
33
|
id: 'w1',
|
|
@@ -49,17 +47,6 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
49
47
|
expect(problems[0]!.message).toContain('duplicate alias');
|
|
50
48
|
});
|
|
51
49
|
|
|
52
|
-
test('flags relation endpoints that reference no component', () => {
|
|
53
|
-
const problems = validateSubsystemModelCrossField(
|
|
54
|
-
doc({
|
|
55
|
-
components: [comp('a')],
|
|
56
|
-
relations: [{ id: 'r1', from: 'a', to: 'ghost', relationType: 'method' }],
|
|
57
|
-
}),
|
|
58
|
-
);
|
|
59
|
-
expect(problems).toHaveLength(1);
|
|
60
|
-
expect(problems[0]!.path).toBe('/relations/0/to');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
50
|
test('flags walkthrough step endpoints that reference no component', () => {
|
|
64
51
|
const problems = validateSubsystemModelCrossField(
|
|
65
52
|
doc({
|
|
@@ -113,4 +100,77 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
113
100
|
),
|
|
114
101
|
).toEqual([]);
|
|
115
102
|
});
|
|
103
|
+
|
|
104
|
+
test('flags a component file pointing into node_modules', () => {
|
|
105
|
+
const problems = validateSubsystemModelCrossField(
|
|
106
|
+
doc({
|
|
107
|
+
components: [
|
|
108
|
+
comp('dep', {
|
|
109
|
+
construct: 'store',
|
|
110
|
+
file: 'packages/react/node_modules/@pierre/diffs/dist/highlighter/shared_highlighter.js',
|
|
111
|
+
}),
|
|
112
|
+
comp('bare', { file: 'node_modules/left-pad/index.js' }),
|
|
113
|
+
// An external may carry such a path too — the path is the defect
|
|
114
|
+
// whichever construct claims it.
|
|
115
|
+
comp('ext', {
|
|
116
|
+
construct: 'external',
|
|
117
|
+
file: 'packages/react/node_modules/@pierre/diffs/dist/components/CodeView.js',
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
expect(problems).toHaveLength(3);
|
|
123
|
+
expect(problems[0]!.path).toBe('/components/0/file');
|
|
124
|
+
expect(problems[0]!.message).toContain('node_modules');
|
|
125
|
+
expect(problems[1]!.path).toBe('/components/1/file');
|
|
126
|
+
expect(problems[2]!.path).toBe('/components/2/file');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('accepts node_modules only as an external/proposed component identity', () => {
|
|
130
|
+
expect(
|
|
131
|
+
validateSubsystemModelCrossField(
|
|
132
|
+
doc({
|
|
133
|
+
components: [
|
|
134
|
+
// A dependency modeled as a package: no file, npm purl.
|
|
135
|
+
comp('dep', {
|
|
136
|
+
construct: 'external',
|
|
137
|
+
file: '',
|
|
138
|
+
purl: 'pkg:npm/@pierre/diffs',
|
|
139
|
+
}),
|
|
140
|
+
// A planned in-repo component may sit where it will live.
|
|
141
|
+
comp('plan', { file: 'packages/x/node_modules/y/z.ts', proposed: true }),
|
|
142
|
+
// `node_modulesx` is an ordinary directory, not the install root.
|
|
143
|
+
comp('ok', { file: 'packages/node_modulesx/z.ts' }),
|
|
144
|
+
],
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('flags a walkthrough step anchored in node_modules', () => {
|
|
151
|
+
const problems = validateSubsystemModelCrossField(
|
|
152
|
+
doc({
|
|
153
|
+
components: [comp('a')],
|
|
154
|
+
walkthroughs: [
|
|
155
|
+
{
|
|
156
|
+
id: 'w1',
|
|
157
|
+
title: 'flow',
|
|
158
|
+
steps: [
|
|
159
|
+
{
|
|
160
|
+
from: 'a',
|
|
161
|
+
to: 'a',
|
|
162
|
+
mechanism: 'calls',
|
|
163
|
+
file: 'packages/react/node_modules/@pierre/diffs/dist/index.js',
|
|
164
|
+
line: 1,
|
|
165
|
+
purl: 'pkg:npm/@pierre/diffs#packages/react/node_modules/@pierre/diffs/dist/index.js',
|
|
166
|
+
symbol: 'a',
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
expect(problems).toHaveLength(1);
|
|
174
|
+
expect(problems[0]!.path).toBe('/walkthroughs/0/steps/0/file');
|
|
175
|
+
});
|
|
116
176
|
});
|
package/src/validation.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These are the rules the JSON Schema (`schemas/subsystem-model.schema.json`)
|
|
5
5
|
* cannot express — anything that spans fields or arrays: alias uniqueness,
|
|
6
|
-
* referential integrity between
|
|
6
|
+
* referential integrity between walkthroughs and components, and the
|
|
7
7
|
* `module` implies `file` invariant. Structural checks (types, `required`,
|
|
8
8
|
* enums, ranges, closed objects) belong to the schema and are enforced per
|
|
9
9
|
* surface; this module owns only what the schema can't.
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
} from './types/subsystem-model';
|
|
19
19
|
|
|
20
20
|
export interface SubsystemValidationProblem {
|
|
21
|
-
/** JSON-pointer-ish location, e.g. `/components/2` or `/
|
|
21
|
+
/** JSON-pointer-ish location, e.g. `/components/2` or `/walkthroughs/0/steps/0/from`. */
|
|
22
22
|
path: string;
|
|
23
23
|
message: string;
|
|
24
24
|
}
|
|
@@ -27,6 +27,25 @@ function isUngrounded(c: SubsystemComponent): boolean {
|
|
|
27
27
|
return c.construct === 'external' || c.proposed === true;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* `file` is a path *inside the repo named by `purl`* — it resolves against that
|
|
32
|
+
* checkout, never against an installed artifact. `node_modules/` is installed,
|
|
33
|
+
* gitignored, and its layout depends on hoisting, so a claim anchored there can
|
|
34
|
+
* never resolve and is unverifiable by construction.
|
|
35
|
+
*
|
|
36
|
+
* Such a component is a third-party dependency: model it as `construct:
|
|
37
|
+
* 'external'` with `purl: 'pkg:npm/<package>'` and no file. Applies to externals
|
|
38
|
+
* too — they carry no file by design, so an install path there is dead weight
|
|
39
|
+
* that draws a link nothing can open. `proposed` is exempt like every other
|
|
40
|
+
* grounding rule: its file is a placeholder for something not placed yet.
|
|
41
|
+
*
|
|
42
|
+
* The same rule covers walkthrough step files — a seam into an external belongs
|
|
43
|
+
* at the call site in the caller, which *is* in the repo.
|
|
44
|
+
*/
|
|
45
|
+
function mentionsNodeModules(path: string): boolean {
|
|
46
|
+
return /(^|\/)node_modules(\/|$)/.test(path);
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
/**
|
|
31
50
|
* Validate the cross-field rules of a subsystem model document. Returns an
|
|
32
51
|
* empty array when the document is consistent.
|
|
@@ -36,11 +55,10 @@ export function validateSubsystemModelCrossField(
|
|
|
36
55
|
): SubsystemValidationProblem[] {
|
|
37
56
|
const problems: SubsystemValidationProblem[] = [];
|
|
38
57
|
const components = doc.components ?? [];
|
|
39
|
-
const relations = doc.relations ?? [];
|
|
40
58
|
const walkthroughs = doc.walkthroughs ?? [];
|
|
41
59
|
|
|
42
60
|
// Component aliases must be unique, and the set is the referential target
|
|
43
|
-
// for
|
|
61
|
+
// for walkthrough steps.
|
|
44
62
|
const ids = new Set<string>();
|
|
45
63
|
components.forEach((c, i) => {
|
|
46
64
|
if (ids.has(c.alias)) {
|
|
@@ -61,19 +79,10 @@ export function validateSubsystemModelCrossField(
|
|
|
61
79
|
message: `component ${JSON.stringify(c.alias)}: module ${JSON.stringify(module)} is set but file is empty — a module frame needs a file to ground it (mark the component proposed if it is not placed yet).`,
|
|
62
80
|
});
|
|
63
81
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
relations.forEach((r, i) => {
|
|
67
|
-
if (!ids.has(r.from)) {
|
|
68
|
-
problems.push({
|
|
69
|
-
path: `/relations/${i}/from`,
|
|
70
|
-
message: `relation ${JSON.stringify(r.id)}: from ${JSON.stringify(r.from)} does not match any component alias`,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
if (!ids.has(r.to)) {
|
|
82
|
+
if (file && c.proposed !== true && mentionsNodeModules(file)) {
|
|
74
83
|
problems.push({
|
|
75
|
-
path: `/
|
|
76
|
-
message: `
|
|
84
|
+
path: `/components/${i}/file`,
|
|
85
|
+
message: `component ${JSON.stringify(c.alias)}: file ${JSON.stringify(file)} points into node_modules — installed artifacts are not part of the repo and cannot be verified. Model the dependency as construct "external" with purl "pkg:npm/<package>" and no file, or anchor the claim to the package's real source.`,
|
|
77
86
|
});
|
|
78
87
|
}
|
|
79
88
|
});
|
|
@@ -104,6 +113,12 @@ export function validateSubsystemModelCrossField(
|
|
|
104
113
|
});
|
|
105
114
|
}
|
|
106
115
|
}
|
|
116
|
+
if (mentionsNodeModules(step.file)) {
|
|
117
|
+
problems.push({
|
|
118
|
+
path: `/walkthroughs/${wi}/steps/${si}/file`,
|
|
119
|
+
message: `walkthrough ${JSON.stringify(w.id)}: step ${si} file ${JSON.stringify(step.file)} points into node_modules — anchor the seam at the call site inside the repo instead.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
107
122
|
});
|
|
108
123
|
});
|
|
109
124
|
|