@principal-ai/subsystems-core 0.48.1 → 0.49.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/storage/topic-types.d.ts +10 -19
- package/dist/storage/topic-types.d.ts.map +1 -1
- package/dist/storage/topic-types.js +9 -12
- package/dist/storage/topic-types.js.map +1 -1
- package/dist/storage/topicStore.d.ts +2 -5
- package/dist/storage/topicStore.d.ts.map +1 -1
- package/dist/storage/topicStore.js +43 -19
- package/dist/storage/topicStore.js.map +1 -1
- package/dist/types/subsystem-model.d.ts +35 -10
- package/dist/types/subsystem-model.d.ts.map +1 -1
- package/dist/types/subsystem-model.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +23 -0
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/schemas/subsystem-model.schema.json +19 -27
- package/src/storage/topic-types.ts +13 -31
- package/src/storage/topicStore.test.ts +60 -24
- package/src/storage/topicStore.ts +60 -47
- package/src/types/subsystem-model.test.ts +12 -2
- package/src/types/subsystem-model.ts +35 -10
- package/src/validation.test.ts +67 -4
- package/src/validation.ts +34 -0
|
@@ -113,13 +113,29 @@ export interface SubsystemDeclToken {
|
|
|
113
113
|
color?: string;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
/**
|
|
117
|
+
* A content-addressed range of source — the model's identity primitive.
|
|
118
|
+
*
|
|
119
|
+
* A span is `startLine` + `endLine` + the hash of the normalized content
|
|
120
|
+
* across that range. Every claim is *anchored* to a span: a component's
|
|
121
|
+
* construct declaration (`declarationSpan`) and a trail step's runtime seam
|
|
122
|
+
* (`span`) are the same shape. `file` / `purl` live on the owner, so a span is
|
|
123
|
+
* a pure range + fingerprint.
|
|
124
|
+
*
|
|
125
|
+
* The hash is the identity: if live content re-hashes to `contentHash`, the
|
|
126
|
+
* anchored code is byte-identical, which is what lets verification reason about
|
|
127
|
+
* constructs rather than line changes.
|
|
128
|
+
*/
|
|
129
|
+
export interface SubsystemSourceSpan {
|
|
130
|
+
/** 1-based first line of the construct/seam within the owner's `file`. */
|
|
119
131
|
startLine: number;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
/** 1-based last line, inclusive. Always `>= startLine`. */
|
|
133
|
+
endLine: number;
|
|
134
|
+
/**
|
|
135
|
+
* SHA-256 of the normalized content across `[startLine, endLine]`, truncated
|
|
136
|
+
* to 32 hex chars. Produced by `hashContentRange`.
|
|
137
|
+
*/
|
|
138
|
+
contentHash: string;
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
export interface SubsystemParamInfo {
|
|
@@ -376,8 +392,12 @@ export interface SubsystemComponent {
|
|
|
376
392
|
*/
|
|
377
393
|
declarationProvenance?: SubsystemDeclarationProvenance;
|
|
378
394
|
tokens?: SubsystemDeclToken[];
|
|
379
|
-
/**
|
|
380
|
-
|
|
395
|
+
/**
|
|
396
|
+
* The construct's source span (start/end/content hash) — its anchored
|
|
397
|
+
* identity. Distinct from `declaration`, which is the structured shape.
|
|
398
|
+
* Usually captured by verify tooling, not hand-authored.
|
|
399
|
+
*/
|
|
400
|
+
declarationSpan?: SubsystemSourceSpan;
|
|
381
401
|
}
|
|
382
402
|
|
|
383
403
|
/**
|
|
@@ -399,8 +419,13 @@ export interface SubsystemTrailStep {
|
|
|
399
419
|
/** Runtime seam label (Set B). */
|
|
400
420
|
mechanism: SubsystemTrailMechanism;
|
|
401
421
|
file: string;
|
|
402
|
-
/**
|
|
403
|
-
|
|
422
|
+
/**
|
|
423
|
+
* The seam's source span — the call-site range and its content hash. The
|
|
424
|
+
* trail-step counterpart of a component's `declarationSpan`, captured by
|
|
425
|
+
* verify tooling from the call site. `file` + `symbol` locate the frame;
|
|
426
|
+
* absent until verification anchors it.
|
|
427
|
+
*/
|
|
428
|
+
span?: SubsystemSourceSpan;
|
|
404
429
|
/**
|
|
405
430
|
* File-anchored purl of the seam site (e.g.
|
|
406
431
|
* `pkg:github/owner/name#path/to/file.ts`), mirroring component `purl`.
|
package/src/validation.test.ts
CHANGED
|
@@ -32,7 +32,7 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
32
32
|
{
|
|
33
33
|
id: 'w1',
|
|
34
34
|
title: 'flow',
|
|
35
|
-
steps: [{ from: 'a', to: 'b', mechanism: 'calls', file: 'src/a.ts',
|
|
35
|
+
steps: [{ from: 'a', to: 'b', mechanism: 'calls', file: 'src/a.ts', purl: 'pkg:github/acme/app#src/a.ts', symbol: 'a' }],
|
|
36
36
|
},
|
|
37
37
|
],
|
|
38
38
|
});
|
|
@@ -55,7 +55,7 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
55
55
|
{
|
|
56
56
|
id: 'w1',
|
|
57
57
|
title: 'flow',
|
|
58
|
-
steps: [{ from: 'ghost', to: 'a', mechanism: 'calls', file: 'src/a.ts',
|
|
58
|
+
steps: [{ from: 'ghost', to: 'a', mechanism: 'calls', file: 'src/a.ts', purl: 'pkg:github/acme/app#src/a.ts', symbol: 'ghost' }],
|
|
59
59
|
},
|
|
60
60
|
],
|
|
61
61
|
}),
|
|
@@ -72,7 +72,7 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
72
72
|
{
|
|
73
73
|
id: 'w1',
|
|
74
74
|
title: 'flow',
|
|
75
|
-
steps: [{ from: 'a', to: 'a', mechanism: 'calls', file: 'src/a.ts',
|
|
75
|
+
steps: [{ from: 'a', to: 'a', mechanism: 'calls', file: 'src/a.ts', purl: 'pkg:github/acme/app#src/other.ts', symbol: 'a' }],
|
|
76
76
|
},
|
|
77
77
|
],
|
|
78
78
|
}),
|
|
@@ -161,7 +161,6 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
161
161
|
to: 'a',
|
|
162
162
|
mechanism: 'calls',
|
|
163
163
|
file: 'packages/react/node_modules/@pierre/diffs/dist/index.js',
|
|
164
|
-
line: 1,
|
|
165
164
|
purl: 'pkg:npm/@pierre/diffs#packages/react/node_modules/@pierre/diffs/dist/index.js',
|
|
166
165
|
symbol: 'a',
|
|
167
166
|
},
|
|
@@ -173,4 +172,68 @@ describe('validateSubsystemModelCrossField', () => {
|
|
|
173
172
|
expect(problems).toHaveLength(1);
|
|
174
173
|
expect(problems[0]!.path).toBe('/trails/0/steps/0/file');
|
|
175
174
|
});
|
|
175
|
+
|
|
176
|
+
test('accepts an ordered span on a component and a step', () => {
|
|
177
|
+
expect(
|
|
178
|
+
validateSubsystemModelCrossField(
|
|
179
|
+
doc({
|
|
180
|
+
components: [
|
|
181
|
+
comp('a', {
|
|
182
|
+
declarationSpan: { startLine: 1, endLine: 9, contentHash: 'h' },
|
|
183
|
+
}),
|
|
184
|
+
],
|
|
185
|
+
trails: [
|
|
186
|
+
{
|
|
187
|
+
id: 'w1',
|
|
188
|
+
title: 'flow',
|
|
189
|
+
steps: [
|
|
190
|
+
{
|
|
191
|
+
from: 'a',
|
|
192
|
+
to: 'a',
|
|
193
|
+
mechanism: 'calls',
|
|
194
|
+
file: 'src/a.ts',
|
|
195
|
+
purl: 'pkg:github/acme/app#src/a.ts',
|
|
196
|
+
symbol: 'a',
|
|
197
|
+
span: { startLine: 3, endLine: 3, contentHash: 'h' },
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
}),
|
|
203
|
+
),
|
|
204
|
+
).toEqual([]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('flags a span whose endLine precedes startLine', () => {
|
|
208
|
+
const problems = validateSubsystemModelCrossField(
|
|
209
|
+
doc({
|
|
210
|
+
components: [
|
|
211
|
+
comp('a', {
|
|
212
|
+
declarationSpan: { startLine: 10, endLine: 4, contentHash: 'h' },
|
|
213
|
+
}),
|
|
214
|
+
],
|
|
215
|
+
trails: [
|
|
216
|
+
{
|
|
217
|
+
id: 'w1',
|
|
218
|
+
title: 'flow',
|
|
219
|
+
steps: [
|
|
220
|
+
{
|
|
221
|
+
from: 'a',
|
|
222
|
+
to: 'a',
|
|
223
|
+
mechanism: 'calls',
|
|
224
|
+
file: 'src/a.ts',
|
|
225
|
+
purl: 'pkg:github/acme/app#src/a.ts',
|
|
226
|
+
symbol: 'a',
|
|
227
|
+
span: { startLine: 8, endLine: 2, contentHash: 'h' },
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
expect(problems).toHaveLength(2);
|
|
235
|
+
expect(problems[0]!.path).toBe('/components/0/declarationSpan');
|
|
236
|
+
expect(problems[0]!.message).toContain('precedes startLine');
|
|
237
|
+
expect(problems[1]!.path).toBe('/trails/0/steps/0/span');
|
|
238
|
+
});
|
|
176
239
|
});
|
package/src/validation.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type {
|
|
15
15
|
SubsystemModelDocument,
|
|
16
16
|
SubsystemComponent,
|
|
17
|
+
SubsystemSourceSpan,
|
|
17
18
|
SubsystemTrail,
|
|
18
19
|
} from './types/subsystem-model';
|
|
19
20
|
|
|
@@ -53,6 +54,27 @@ function mentionsNodeModules(path: string): boolean {
|
|
|
53
54
|
return /(^|\/)node_modules(\/|$)/.test(path);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* A span's range must be ordered: `endLine >= startLine`. The schema enforces
|
|
59
|
+
* that each line is a positive integer but cannot compare two fields, so the
|
|
60
|
+
* ordering rule lives here. `owner` prefixes the message (e.g.
|
|
61
|
+
* `component "save"`).
|
|
62
|
+
*/
|
|
63
|
+
function spanRangeProblem(
|
|
64
|
+
span: SubsystemSourceSpan | undefined,
|
|
65
|
+
path: string,
|
|
66
|
+
owner: string,
|
|
67
|
+
): SubsystemValidationProblem | null {
|
|
68
|
+
if (!span) return null;
|
|
69
|
+
if (span.endLine < span.startLine) {
|
|
70
|
+
return {
|
|
71
|
+
path,
|
|
72
|
+
message: `${owner}: span endLine ${span.endLine} precedes startLine ${span.startLine} — a span range must be ordered (endLine >= startLine).`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
/**
|
|
57
79
|
* Validate the cross-field rules of a subsystem model document. Returns an
|
|
58
80
|
* empty array when the document is consistent.
|
|
@@ -97,6 +119,12 @@ export function validateSubsystemModelCrossField(
|
|
|
97
119
|
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.`,
|
|
98
120
|
});
|
|
99
121
|
}
|
|
122
|
+
const spanProblem = spanRangeProblem(
|
|
123
|
+
c.declarationSpan,
|
|
124
|
+
`/components/${i}/declarationSpan`,
|
|
125
|
+
`component ${JSON.stringify(c.alias)}`,
|
|
126
|
+
);
|
|
127
|
+
if (spanProblem) problems.push(spanProblem);
|
|
100
128
|
});
|
|
101
129
|
|
|
102
130
|
trails.forEach((w: SubsystemTrail, ti) => {
|
|
@@ -131,6 +159,12 @@ export function validateSubsystemModelCrossField(
|
|
|
131
159
|
message: `trail ${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.`,
|
|
132
160
|
});
|
|
133
161
|
}
|
|
162
|
+
const spanProblem = spanRangeProblem(
|
|
163
|
+
step.span,
|
|
164
|
+
`/trails/${ti}/steps/${si}/span`,
|
|
165
|
+
`trail ${JSON.stringify(w.id)}: step ${si}`,
|
|
166
|
+
);
|
|
167
|
+
if (spanProblem) problems.push(spanProblem);
|
|
134
168
|
});
|
|
135
169
|
});
|
|
136
170
|
|