@principal-ai/subsystems-core 0.34.1 → 0.35.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/index.d.ts CHANGED
@@ -11,4 +11,6 @@
11
11
  * import { ... } from '@principal-ai/subsystems-core/pipeline'
12
12
  */
13
13
  export * from './types';
14
+ export { validateSubsystemModelCrossField } from './validation';
15
+ export type { SubsystemValidationProblem } from './validation';
14
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,SAAS,CAAC;AAKxB,OAAO,EAAE,gCAAgC,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -26,5 +26,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
26
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.validateSubsystemModelCrossField = void 0;
29
30
  __exportStar(require("./types"), exports);
31
+ // Cross-field validation (the rules the JSON Schema can't express). Structural
32
+ // (schema) validation is enforced per surface against
33
+ // `schemas/subsystem-model.schema.json`.
34
+ var validation_1 = require("./validation");
35
+ Object.defineProperty(exports, "validateSubsystemModelCrossField", { enumerable: true, get: function () { return validation_1.validateSubsystemModelCrossField; } });
30
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;;;;;;;;;;;;;;AAEH,0CAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;;;;;;;;;;;;;;;AAEH,0CAAwB;AAExB,+EAA+E;AAC/E,sDAAsD;AACtD,yCAAyC;AACzC,2CAAgE;AAAvD,8HAAA,gCAAgC,OAAA"}
@@ -15,6 +15,8 @@
15
15
  * framework pattern it plays, role = where it sits, process = where it runs,
16
16
  * module = which source file/module the export belongs to,
17
17
  * proposed = not yet in source (design / migration placeholder).
18
+ * Package (repo) frames are derived from component `purl` when a graph spans
19
+ * multiple repos — there is no separate `package` field.
18
20
  * `symbol` is the code identity; `name` is the display label.
19
21
  */
20
22
  /** What the node IS as a declaration. `module` is not an authored construct —
@@ -200,13 +202,20 @@ export interface SubsystemCustomEntityDeclaration {
200
202
  export type SubsystemConstructDeclaration = SubsystemClassDeclaration | SubsystemFunctionDeclaration | SubsystemMethodDeclaration | SubsystemTypeDeclaration | SubsystemExternalDeclaration | SubsystemStoreDeclaration | SubsystemCustomEntityDeclaration;
201
203
  /** A component node — the named unit, construct-tagged. */
202
204
  export interface SubsystemComponent {
203
- id: string;
205
+ /**
206
+ * Model-local stable alias. Referenced by relation / walkthrough `from` /
207
+ * `to`; unique per model. Edges point at the alias, not the location — a
208
+ * file move or symbol rename leaves edges intact. Code identity lives on
209
+ * `purl` + `file` + `symbol` and is what composed (multi-model) views
210
+ * join on, not this field.
211
+ */
212
+ alias: string;
204
213
  /** Display label. Prefer aligning with `symbol` when present. */
205
214
  name: string;
206
215
  construct: SubsystemConstruct;
207
216
  /** Repo-root-relative source path. */
208
217
  file: string;
209
- /** PURL for repo/package grouping. */
218
+ /** PURL for repo/package grouping. Multi-repo graphs draw package frames from distinct purl repo keys. */
210
219
  purl: string;
211
220
  purpose?: string;
212
221
  role?: SubsystemComponentRole;
@@ -260,9 +269,9 @@ export interface SubsystemComponent {
260
269
  /** A topology relation between components (structural / module / type). */
261
270
  export interface SubsystemRelation {
262
271
  id: string;
263
- /** Source component id. */
272
+ /** Source component alias. */
264
273
  from: string;
265
- /** Target component id (or external label). */
274
+ /** Target component alias (or external label). */
266
275
  to: string;
267
276
  relationType: SubsystemRelationType;
268
277
  /** Concrete file/symbol evidence (often purls). */
@@ -280,9 +289,9 @@ export interface SubsystemComponentEdge {
280
289
  refs?: string[];
281
290
  }
282
291
  export interface SubsystemWalkthroughStep {
283
- /** Source component id. */
292
+ /** Source component alias. */
284
293
  from: string;
285
- /** Target component id. */
294
+ /** Target component alias. */
286
295
  to: string;
287
296
  /** Runtime seam label (Set B). */
288
297
  mechanism: SubsystemWalkthroughMechanism;
@@ -304,10 +313,6 @@ export interface SubsystemWalkthrough {
304
313
  title: string;
305
314
  steps: SubsystemWalkthroughStep[];
306
315
  }
307
- export interface SubsystemRepoRef {
308
- owner: string;
309
- name: string;
310
- }
311
316
  /**
312
317
  * Portable subsystem model — the shareable standard.
313
318
  * No host paths, provenance, document-level repo, store ids, or verification.
@@ -328,33 +333,12 @@ export interface SubsystemModelDocument {
328
333
  walkthroughs?: SubsystemWalkthrough[];
329
334
  }
330
335
  /**
331
- * Host/machine fields layered onto a portable document for local use.
332
- * Not part of the portable standard — viewers and stores own this envelope.
333
- */
334
- export interface SubsystemModelHostBinding {
335
- /** Provenance of the model (e.g. `agent:<name>`, `manual`). */
336
- source?: string;
337
- /**
338
- * Optional document-level repo summary for host indexes/listings.
339
- * Portable identity lives on each component's `purl`.
340
- */
341
- repo?: SubsystemRepoRef;
342
- /**
343
- * Local filesystem root component `file` paths resolve against.
344
- * Opt-in; sandboxed reads for single-repo models.
345
- */
346
- repoRoot?: string;
347
- /**
348
- * Per-repo local roots for multi-repo models, keyed by purl repo key
349
- * (`pkg:github/owner/name`, fragment stripped).
350
- */
351
- repoRoots?: Record<string, string>;
352
- }
353
- /**
354
- * Hydrated model: portable document + host binding + optional store metadata.
336
+ * Hydrated model: portable document + store metadata.
355
337
  * Shape used after a host accepts/persists a model for viewing on a machine.
338
+ * Local path binding is resolved from Alexandria (via each component's purl)
339
+ * and repo identity is derived from purls — neither is stored here.
356
340
  */
357
- export interface SubsystemModelHydrated extends SubsystemModelDocument, SubsystemModelHostBinding {
341
+ export interface SubsystemModelHydrated extends SubsystemModelDocument {
358
342
  /** Store-assigned id when persisted. */
359
343
  id?: string;
360
344
  createdAt?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"subsystem-model.d.ts","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subsystem-model.d.ts","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,8 @@
16
16
  * framework pattern it plays, role = where it sits, process = where it runs,
17
17
  * module = which source file/module the export belongs to,
18
18
  * proposed = not yet in source (design / migration placeholder).
19
+ * Package (repo) frames are derived from component `purl` when a graph spans
20
+ * multiple repos — there is no separate `package` field.
19
21
  * `symbol` is the code identity; `name` is the display label.
20
22
  */
21
23
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1 +1 @@
1
- {"version":3,"file":"subsystem-model.js","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AA2aH;;;;GAIG;AACH,SAAgB,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,KAIT,CAAC;IACF,OAAO,CACL,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAC3B,CAAC;AACJ,CAAC;AAZD,4DAYC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAChC,GAA2B;IAE3B,MAAM,GAAG,GAA2B;QAClC,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,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,OAAO,GAAG,CAAC;AACb,CAAC;AAZD,gDAYC;AAED,6EAA6E;AAC7E,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,GAGhC;IACC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkC,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,EAAE;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,kBAAkB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC;QACpE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;YACjB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE;gBACX,EAAE;gBACF,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,SAAS,EAAE,CAAC,CAAC,YAAY;gBACzB,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;SACJ;KACF;IACD,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;AA/BD,4CA+BC"}
1
+ {"version":3,"file":"subsystem-model.js","sourceRoot":"","sources":["../../src/types/subsystem-model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAsZH;;;;GAIG;AACH,SAAgB,wBAAwB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,KAIT,CAAC;IACF,OAAO,CACL,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;QAC3B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAC3B,CAAC;AACJ,CAAC;AAZD,4DAYC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAChC,GAA2B;IAE3B,MAAM,GAAG,GAA2B;QAClC,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,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,OAAO,GAAG,CAAC;AACb,CAAC;AAZD,gDAYC;AAED,6EAA6E;AAC7E,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,GAGhC;IACC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkC,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,EAAE;QACnC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,kBAAkB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC;QACpE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;YACjB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE;gBACX,EAAE;gBACF,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,SAAS,EAAE,CAAC,CAAC,YAAY;gBACzB,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;SACJ;KACF;IACD,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;AA/BD,4CA+BC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Cross-field validation for a subsystem model document.
3
+ *
4
+ * These are the rules the JSON Schema (`schemas/subsystem-model.schema.json`)
5
+ * cannot express — anything that spans fields or arrays: alias uniqueness,
6
+ * referential integrity between relations/walkthroughs and components, and the
7
+ * `module` implies `file` invariant. Structural checks (types, `required`,
8
+ * enums, ranges, closed objects) belong to the schema and are enforced per
9
+ * surface; this module owns only what the schema can't.
10
+ *
11
+ * Pure and dependency-free (browser-safe) — no JSON Schema validator here.
12
+ */
13
+ import type { SubsystemModelDocument } from './types/subsystem-model';
14
+ export interface SubsystemValidationProblem {
15
+ /** JSON-pointer-ish location, e.g. `/components/2` or `/relations/0/from`. */
16
+ path: string;
17
+ message: string;
18
+ }
19
+ /**
20
+ * Validate the cross-field rules of a subsystem model document. Returns an
21
+ * empty array when the document is consistent.
22
+ */
23
+ export declare function validateSubsystemModelCrossField(doc: SubsystemModelDocument): SubsystemValidationProblem[];
24
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +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,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,sBAAsB,GAC1B,0BAA0B,EAAE,CA+D9B"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Cross-field validation for a subsystem model document.
4
+ *
5
+ * These are the rules the JSON Schema (`schemas/subsystem-model.schema.json`)
6
+ * cannot express — anything that spans fields or arrays: alias uniqueness,
7
+ * referential integrity between relations/walkthroughs and components, and the
8
+ * `module` implies `file` invariant. Structural checks (types, `required`,
9
+ * enums, ranges, closed objects) belong to the schema and are enforced per
10
+ * surface; this module owns only what the schema can't.
11
+ *
12
+ * Pure and dependency-free (browser-safe) — no JSON Schema validator here.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.validateSubsystemModelCrossField = void 0;
16
+ function isUngrounded(c) {
17
+ return c.construct === 'external' || c.proposed === true;
18
+ }
19
+ /**
20
+ * Validate the cross-field rules of a subsystem model document. Returns an
21
+ * empty array when the document is consistent.
22
+ */
23
+ function validateSubsystemModelCrossField(doc) {
24
+ const problems = [];
25
+ const components = doc.components ?? [];
26
+ const relations = doc.relations ?? [];
27
+ const walkthroughs = doc.walkthroughs ?? [];
28
+ // Component aliases must be unique, and the set is the referential target
29
+ // for relations and walkthrough steps.
30
+ const ids = new Set();
31
+ components.forEach((c, i) => {
32
+ if (ids.has(c.alias)) {
33
+ problems.push({
34
+ path: `/components/${i}/alias`,
35
+ message: `component ${JSON.stringify(c.alias)}: duplicate alias`,
36
+ });
37
+ }
38
+ else {
39
+ ids.add(c.alias);
40
+ }
41
+ // A grounded component that claims a `module` needs a `file` to ground the
42
+ // frame. Externals / proposed carry no file by design.
43
+ const module = typeof c.module === 'string' ? c.module.trim() : '';
44
+ const file = typeof c.file === 'string' ? c.file.trim() : '';
45
+ if (module && !file && !isUngrounded(c)) {
46
+ problems.push({
47
+ path: `/components/${i}/module`,
48
+ 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
+ });
50
+ }
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)) {
60
+ problems.push({
61
+ path: `/relations/${i}/to`,
62
+ message: `relation ${JSON.stringify(r.id)}: to ${JSON.stringify(r.to)} does not match any component alias`,
63
+ });
64
+ }
65
+ });
66
+ walkthroughs.forEach((w, wi) => {
67
+ (w.steps ?? []).forEach((step, si) => {
68
+ if (!ids.has(step.from)) {
69
+ problems.push({
70
+ path: `/walkthroughs/${wi}/steps/${si}/from`,
71
+ message: `walkthrough ${JSON.stringify(w.id)}: step ${si} from ${JSON.stringify(step.from)} does not match any component alias`,
72
+ });
73
+ }
74
+ if (!ids.has(step.to)) {
75
+ problems.push({
76
+ path: `/walkthroughs/${wi}/steps/${si}/to`,
77
+ message: `walkthrough ${JSON.stringify(w.id)}: step ${si} to ${JSON.stringify(step.to)} does not match any component alias`,
78
+ });
79
+ }
80
+ });
81
+ });
82
+ return problems;
83
+ }
84
+ exports.validateSubsystemModelCrossField = validateSubsystemModelCrossField;
85
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
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.34.1",
3
+ "version": "0.35.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",
@@ -22,7 +22,8 @@
22
22
  "import": "./dist/opencode/pipeline.js",
23
23
  "require": "./dist/opencode/pipeline.js",
24
24
  "default": "./dist/opencode/pipeline.js"
25
- }
25
+ },
26
+ "./schemas/subsystem-model.schema.json": "./schemas/subsystem-model.schema.json"
26
27
  },
27
28
  "files": [
28
29
  "dist",
@@ -64,4 +65,4 @@
64
65
  "access": "public",
65
66
  "registry": "https://registry.npmjs.org/"
66
67
  }
67
- }
68
+ }
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "relations": {
35
35
  "type": "array",
36
- "description": "Topology relations (structural / module / type). `from` / `to` reference component `id`s. Runtime seams belong on walkthrough steps, not here.",
36
+ "description": "Topology relations (structural / module / type). `from` / `to` reference component `alias`es. Runtime seams belong on walkthrough steps, not here.",
37
37
  "items": {
38
38
  "$ref": "#/$defs/relation"
39
39
  }
@@ -122,7 +122,7 @@
122
122
  "component": {
123
123
  "type": "object",
124
124
  "required": [
125
- "id",
125
+ "alias",
126
126
  "name",
127
127
  "construct",
128
128
  "file",
@@ -130,10 +130,10 @@
130
130
  ],
131
131
  "additionalProperties": false,
132
132
  "properties": {
133
- "id": {
133
+ "alias": {
134
134
  "type": "string",
135
135
  "minLength": 1,
136
- "description": "Stable unique id. Referenced by relation / walkthrough `from` / `to`; never rename on update."
136
+ "description": "Model-local stable alias, unique per model. Referenced by relation / 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."
137
137
  },
138
138
  "name": {
139
139
  "type": "string",
@@ -145,12 +145,12 @@
145
145
  },
146
146
  "file": {
147
147
  "type": "string",
148
- "description": "Repo-root-relative path of the source location this component lives in. Joins onto `repoRoot` / `repoRoots` for reads. Empty string allowed for pure externals."
148
+ "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."
149
149
  },
150
150
  "purl": {
151
151
  "type": "string",
152
152
  "minLength": 1,
153
- "description": "PURL identifying the repo or package (for subgraph grouping), e.g. `pkg:github/owner/name#path/to/file.ts`."
153
+ "description": "PURL identifying the repo or package (for subgraph grouping), e.g. `pkg:github/owner/name#path/to/file.ts`. When a graph spans multiple repos, distinct purl repo keys become package boundary frames (package → process → module nesting). Single-repo graphs do not draw package frames by default."
154
154
  },
155
155
  "symbol": {
156
156
  "type": "string",
@@ -182,11 +182,11 @@
182
182
  },
183
183
  "process": {
184
184
  "type": "string",
185
- "description": "Runtime process / deployment unit membership (e.g. `principal-studio/host`). Nodes sharing a process are drawn in one boundary region; nodes without one sit outside every boundary."
185
+ "description": "Runtime process / deployment unit membership (e.g. `principal-studio/host`). Nodes sharing a process are drawn in one boundary region; nodes without one sit outside every process boundary. Nests inside a package frame when the graph is multi-repo."
186
186
  },
187
187
  "module": {
188
188
  "type": "string",
189
- "description": "Source-module membership — which file/module this export belongs to (e.g. `src/session/transcript.ts`). Nodes sharing a module are drawn in one boundary frame. Prefer anchoring each export as its real construct and setting module, rather than a module construct."
189
+ "description": "Source-module membership — which file/module this export belongs to (e.g. `src/session/transcript.ts`). Nodes sharing a module are drawn in one boundary frame. Prefer anchoring each export as its real construct and setting module, rather than a module construct. Nests under process (and package when multi-repo)."
190
190
  },
191
191
  "layer": {
192
192
  "type": "integer",
@@ -230,12 +230,12 @@
230
230
  "from": {
231
231
  "type": "string",
232
232
  "minLength": 1,
233
- "description": "Source component `id`."
233
+ "description": "Source component `alias`."
234
234
  },
235
235
  "to": {
236
236
  "type": "string",
237
237
  "minLength": 1,
238
- "description": "Target component `id` (or external target label)."
238
+ "description": "Target component `alias` (or external target label)."
239
239
  },
240
240
  "relationType": {
241
241
  "$ref": "#/$defs/relationType"
@@ -264,12 +264,12 @@
264
264
  "from": {
265
265
  "type": "string",
266
266
  "minLength": 1,
267
- "description": "Source component `id`."
267
+ "description": "Source component `alias`."
268
268
  },
269
269
  "to": {
270
270
  "type": "string",
271
271
  "minLength": 1,
272
- "description": "Target component `id`."
272
+ "description": "Target component `alias`."
273
273
  },
274
274
  "mechanism": {
275
275
  "$ref": "#/$defs/mechanism"
package/src/index.ts CHANGED
@@ -12,3 +12,9 @@
12
12
  */
13
13
 
14
14
  export * from './types';
15
+
16
+ // Cross-field validation (the rules the JSON Schema can't express). Structural
17
+ // (schema) validation is enforced per surface against
18
+ // `schemas/subsystem-model.schema.json`.
19
+ export { validateSubsystemModelCrossField } from './validation';
20
+ export type { SubsystemValidationProblem } from './validation';
@@ -15,6 +15,8 @@
15
15
  * framework pattern it plays, role = where it sits, process = where it runs,
16
16
  * module = which source file/module the export belongs to,
17
17
  * proposed = not yet in source (design / migration placeholder).
18
+ * Package (repo) frames are derived from component `purl` when a graph spans
19
+ * multiple repos — there is no separate `package` field.
18
20
  * `symbol` is the code identity; `name` is the display label.
19
21
  */
20
22
 
@@ -270,13 +272,20 @@ export type SubsystemConstructDeclaration =
270
272
 
271
273
  /** A component node — the named unit, construct-tagged. */
272
274
  export interface SubsystemComponent {
273
- id: string;
275
+ /**
276
+ * Model-local stable alias. Referenced by relation / walkthrough `from` /
277
+ * `to`; unique per model. Edges point at the alias, not the location — a
278
+ * file move or symbol rename leaves edges intact. Code identity lives on
279
+ * `purl` + `file` + `symbol` and is what composed (multi-model) views
280
+ * join on, not this field.
281
+ */
282
+ alias: string;
274
283
  /** Display label. Prefer aligning with `symbol` when present. */
275
284
  name: string;
276
285
  construct: SubsystemConstruct;
277
286
  /** Repo-root-relative source path. */
278
287
  file: string;
279
- /** PURL for repo/package grouping. */
288
+ /** PURL for repo/package grouping. Multi-repo graphs draw package frames from distinct purl repo keys. */
280
289
  purl: string;
281
290
  purpose?: string;
282
291
  role?: SubsystemComponentRole;
@@ -331,9 +340,9 @@ export interface SubsystemComponent {
331
340
  /** A topology relation between components (structural / module / type). */
332
341
  export interface SubsystemRelation {
333
342
  id: string;
334
- /** Source component id. */
343
+ /** Source component alias. */
335
344
  from: string;
336
- /** Target component id (or external label). */
345
+ /** Target component alias (or external label). */
337
346
  to: string;
338
347
  relationType: SubsystemRelationType;
339
348
  /** Concrete file/symbol evidence (often purls). */
@@ -353,9 +362,9 @@ export interface SubsystemComponentEdge {
353
362
  }
354
363
 
355
364
  export interface SubsystemWalkthroughStep {
356
- /** Source component id. */
365
+ /** Source component alias. */
357
366
  from: string;
358
- /** Target component id. */
367
+ /** Target component alias. */
359
368
  to: string;
360
369
  /** Runtime seam label (Set B). */
361
370
  mechanism: SubsystemWalkthroughMechanism;
@@ -379,11 +388,6 @@ export interface SubsystemWalkthrough {
379
388
  steps: SubsystemWalkthroughStep[];
380
389
  }
381
390
 
382
- export interface SubsystemRepoRef {
383
- owner: string;
384
- name: string;
385
- }
386
-
387
391
  /**
388
392
  * Portable subsystem model — the shareable standard.
389
393
  * No host paths, provenance, document-level repo, store ids, or verification.
@@ -406,35 +410,12 @@ export interface SubsystemModelDocument {
406
410
  }
407
411
 
408
412
  /**
409
- * Host/machine fields layered onto a portable document for local use.
410
- * Not part of the portable standard — viewers and stores own this envelope.
411
- */
412
- export interface SubsystemModelHostBinding {
413
- /** Provenance of the model (e.g. `agent:<name>`, `manual`). */
414
- source?: string;
415
- /**
416
- * Optional document-level repo summary for host indexes/listings.
417
- * Portable identity lives on each component's `purl`.
418
- */
419
- repo?: SubsystemRepoRef;
420
- /**
421
- * Local filesystem root component `file` paths resolve against.
422
- * Opt-in; sandboxed reads for single-repo models.
423
- */
424
- repoRoot?: string;
425
- /**
426
- * Per-repo local roots for multi-repo models, keyed by purl repo key
427
- * (`pkg:github/owner/name`, fragment stripped).
428
- */
429
- repoRoots?: Record<string, string>;
430
- }
431
-
432
- /**
433
- * Hydrated model: portable document + host binding + optional store metadata.
413
+ * Hydrated model: portable document + store metadata.
434
414
  * Shape used after a host accepts/persists a model for viewing on a machine.
415
+ * Local path binding is resolved from Alexandria (via each component's purl)
416
+ * and repo identity is derived from purls — neither is stored here.
435
417
  */
436
- export interface SubsystemModelHydrated
437
- extends SubsystemModelDocument, SubsystemModelHostBinding {
418
+ export interface SubsystemModelHydrated extends SubsystemModelDocument {
438
419
  /** Store-assigned id when persisted. */
439
420
  id?: string;
440
421
  createdAt?: string;
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { validateSubsystemModelCrossField } from './validation';
3
+ import type { SubsystemModelDocument } from './types/subsystem-model';
4
+
5
+ function comp(
6
+ alias: string,
7
+ extra: Partial<SubsystemModelDocument['components'][number]> = {},
8
+ ) {
9
+ return {
10
+ alias,
11
+ name: alias,
12
+ construct: 'function' as const,
13
+ file: `src/${alias}.ts`,
14
+ purl: 'pkg:github/acme/app',
15
+ ...extra,
16
+ };
17
+ }
18
+
19
+ function doc(partial: Partial<SubsystemModelDocument>): SubsystemModelDocument {
20
+ return {
21
+ title: 't',
22
+ components: [],
23
+ relations: [],
24
+ ...partial,
25
+ } as SubsystemModelDocument;
26
+ }
27
+
28
+ describe('validateSubsystemModelCrossField', () => {
29
+ test('accepts a consistent document', () => {
30
+ const d = doc({
31
+ components: [comp('a'), comp('b')],
32
+ relations: [{ id: 'r1', from: 'a', to: 'b', relationType: 'imports' }],
33
+ walkthroughs: [
34
+ {
35
+ id: 'w1',
36
+ title: 'flow',
37
+ steps: [{ from: 'a', to: 'b', mechanism: 'calls', file: 'src/a.ts', line: 1 }],
38
+ },
39
+ ],
40
+ });
41
+ expect(validateSubsystemModelCrossField(d)).toEqual([]);
42
+ });
43
+
44
+ test('flags duplicate component aliases', () => {
45
+ const problems = validateSubsystemModelCrossField(
46
+ doc({ components: [comp('a'), comp('a')] }),
47
+ );
48
+ expect(problems).toHaveLength(1);
49
+ expect(problems[0]!.message).toContain('duplicate alias');
50
+ });
51
+
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: 'imports' }],
57
+ }),
58
+ );
59
+ expect(problems).toHaveLength(1);
60
+ expect(problems[0]!.path).toBe('/relations/0/to');
61
+ });
62
+
63
+ test('flags walkthrough step endpoints that reference no component', () => {
64
+ const problems = validateSubsystemModelCrossField(
65
+ doc({
66
+ components: [comp('a')],
67
+ walkthroughs: [
68
+ {
69
+ id: 'w1',
70
+ title: 'flow',
71
+ steps: [{ from: 'ghost', to: 'a', mechanism: 'calls', file: 'src/a.ts', line: 1 }],
72
+ },
73
+ ],
74
+ }),
75
+ );
76
+ expect(problems).toHaveLength(1);
77
+ expect(problems[0]!.path).toBe('/walkthroughs/0/steps/0/from');
78
+ });
79
+
80
+ test('module implies file, exempting external/proposed', () => {
81
+ const bad = validateSubsystemModelCrossField(
82
+ doc({ components: [comp('a', { module: 'src/host', file: '' })] }),
83
+ );
84
+ expect(bad).toHaveLength(1);
85
+ expect(bad[0]!.message).toContain('file is empty');
86
+
87
+ expect(
88
+ validateSubsystemModelCrossField(
89
+ doc({
90
+ components: [
91
+ comp('ext', { module: 'pkg:npm/x', construct: 'external', file: '' }),
92
+ comp('plan', { module: 'src/host', file: '', proposed: true }),
93
+ comp('ok', { module: 'src/host', file: 'src/host/main.ts' }),
94
+ ],
95
+ }),
96
+ ),
97
+ ).toEqual([]);
98
+ });
99
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Cross-field validation for a subsystem model document.
3
+ *
4
+ * These are the rules the JSON Schema (`schemas/subsystem-model.schema.json`)
5
+ * cannot express — anything that spans fields or arrays: alias uniqueness,
6
+ * referential integrity between relations/walkthroughs and components, and the
7
+ * `module` implies `file` invariant. Structural checks (types, `required`,
8
+ * enums, ranges, closed objects) belong to the schema and are enforced per
9
+ * surface; this module owns only what the schema can't.
10
+ *
11
+ * Pure and dependency-free (browser-safe) — no JSON Schema validator here.
12
+ */
13
+
14
+ import type {
15
+ SubsystemModelDocument,
16
+ SubsystemComponent,
17
+ SubsystemWalkthrough,
18
+ } from './types/subsystem-model';
19
+
20
+ export interface SubsystemValidationProblem {
21
+ /** JSON-pointer-ish location, e.g. `/components/2` or `/relations/0/from`. */
22
+ path: string;
23
+ message: string;
24
+ }
25
+
26
+ function isUngrounded(c: SubsystemComponent): boolean {
27
+ return c.construct === 'external' || c.proposed === true;
28
+ }
29
+
30
+ /**
31
+ * Validate the cross-field rules of a subsystem model document. Returns an
32
+ * empty array when the document is consistent.
33
+ */
34
+ export function validateSubsystemModelCrossField(
35
+ doc: SubsystemModelDocument,
36
+ ): SubsystemValidationProblem[] {
37
+ const problems: SubsystemValidationProblem[] = [];
38
+ const components = doc.components ?? [];
39
+ const relations = doc.relations ?? [];
40
+ const walkthroughs = doc.walkthroughs ?? [];
41
+
42
+ // Component aliases must be unique, and the set is the referential target
43
+ // for relations and walkthrough steps.
44
+ const ids = new Set<string>();
45
+ components.forEach((c, i) => {
46
+ if (ids.has(c.alias)) {
47
+ problems.push({
48
+ path: `/components/${i}/alias`,
49
+ message: `component ${JSON.stringify(c.alias)}: duplicate alias`,
50
+ });
51
+ } else {
52
+ ids.add(c.alias);
53
+ }
54
+ // A grounded component that claims a `module` needs a `file` to ground the
55
+ // frame. Externals / proposed carry no file by design.
56
+ const module = typeof c.module === 'string' ? c.module.trim() : '';
57
+ const file = typeof c.file === 'string' ? c.file.trim() : '';
58
+ if (module && !file && !isUngrounded(c)) {
59
+ problems.push({
60
+ path: `/components/${i}/module`,
61
+ 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
+ });
63
+ }
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)) {
74
+ problems.push({
75
+ path: `/relations/${i}/to`,
76
+ message: `relation ${JSON.stringify(r.id)}: to ${JSON.stringify(r.to)} does not match any component alias`,
77
+ });
78
+ }
79
+ });
80
+
81
+ walkthroughs.forEach((w: SubsystemWalkthrough, wi) => {
82
+ (w.steps ?? []).forEach((step, si) => {
83
+ if (!ids.has(step.from)) {
84
+ problems.push({
85
+ path: `/walkthroughs/${wi}/steps/${si}/from`,
86
+ message: `walkthrough ${JSON.stringify(w.id)}: step ${si} from ${JSON.stringify(step.from)} does not match any component alias`,
87
+ });
88
+ }
89
+ if (!ids.has(step.to)) {
90
+ problems.push({
91
+ path: `/walkthroughs/${wi}/steps/${si}/to`,
92
+ message: `walkthrough ${JSON.stringify(w.id)}: step ${si} to ${JSON.stringify(step.to)} does not match any component alias`,
93
+ });
94
+ }
95
+ });
96
+ });
97
+
98
+ return problems;
99
+ }