@principal-ai/subsystems-core 0.42.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.
@@ -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,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,sBAAsB,GAC1B,0BAA0B,EAAE,CA2D9B"}
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"}
@@ -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.
@@ -47,6 +65,12 @@ function validateSubsystemModelCrossField(doc) {
47
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).`,
48
66
  });
49
67
  }
68
+ if (file && c.proposed !== true && mentionsNodeModules(file)) {
69
+ problems.push({
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.`,
72
+ });
73
+ }
50
74
  });
51
75
  walkthroughs.forEach((w, wi) => {
52
76
  (w.steps ?? []).forEach((step, si) => {
@@ -74,6 +98,12 @@ function validateSubsystemModelCrossField(doc) {
74
98
  });
75
99
  }
76
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
+ }
77
107
  });
78
108
  });
79
109
  return problems;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/subsystems-core",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "Subsystem model types and agent-session tooling for Principal AI",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -138,7 +138,8 @@
138
138
  },
139
139
  "file": {
140
140
  "type": "string",
141
- "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."
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."
142
143
  },
143
144
  "purl": {
144
145
  "type": "string",
@@ -234,6 +235,7 @@
234
235
  "file": {
235
236
  "type": "string",
236
237
  "minLength": 1,
238
+ "pattern": "^(?!.*(^|/)node_modules(/|$)).*$",
237
239
  "description": "Repo-root-relative path where the seam fires for this walkthrough."
238
240
  },
239
241
  "line": {
@@ -100,4 +100,77 @@ describe('validateSubsystemModelCrossField', () => {
100
100
  ),
101
101
  ).toEqual([]);
102
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
+ });
103
176
  });
package/src/validation.ts CHANGED
@@ -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.
@@ -60,6 +79,12 @@ export function validateSubsystemModelCrossField(
60
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).`,
61
80
  });
62
81
  }
82
+ if (file && c.proposed !== true && mentionsNodeModules(file)) {
83
+ problems.push({
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.`,
86
+ });
87
+ }
63
88
  });
64
89
 
65
90
  walkthroughs.forEach((w: SubsystemWalkthrough, wi) => {
@@ -88,6 +113,12 @@ export function validateSubsystemModelCrossField(
88
113
  });
89
114
  }
90
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
+ }
91
122
  });
92
123
  });
93
124