@clossys/architect 0.1.2

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +171 -0
  4. package/dist/assessment.d.ts +9 -0
  5. package/dist/assessment.d.ts.map +1 -0
  6. package/dist/assessment.js +98 -0
  7. package/dist/assessment.js.map +1 -0
  8. package/dist/cli.d.ts +6 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +96 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +5 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/ontology/compatibility.d.ts +7 -0
  17. package/dist/ontology/compatibility.d.ts.map +1 -0
  18. package/dist/ontology/compatibility.js +135 -0
  19. package/dist/ontology/compatibility.js.map +1 -0
  20. package/dist/ontology/define.d.ts +8 -0
  21. package/dist/ontology/define.d.ts.map +1 -0
  22. package/dist/ontology/define.js +30 -0
  23. package/dist/ontology/define.js.map +1 -0
  24. package/dist/ontology/index.d.ts +9 -0
  25. package/dist/ontology/index.d.ts.map +1 -0
  26. package/dist/ontology/index.js +8 -0
  27. package/dist/ontology/index.js.map +1 -0
  28. package/dist/ontology/normalize.d.ts +6 -0
  29. package/dist/ontology/normalize.d.ts.map +1 -0
  30. package/dist/ontology/normalize.js +60 -0
  31. package/dist/ontology/normalize.js.map +1 -0
  32. package/dist/ontology/snapshot.d.ts +14 -0
  33. package/dist/ontology/snapshot.d.ts.map +1 -0
  34. package/dist/ontology/snapshot.js +299 -0
  35. package/dist/ontology/snapshot.js.map +1 -0
  36. package/dist/ontology/types.d.ts +123 -0
  37. package/dist/ontology/types.d.ts.map +1 -0
  38. package/dist/ontology/types.js +16 -0
  39. package/dist/ontology/types.js.map +1 -0
  40. package/dist/ontology/validate.d.ts +8 -0
  41. package/dist/ontology/validate.d.ts.map +1 -0
  42. package/dist/ontology/validate.js +190 -0
  43. package/dist/ontology/validate.js.map +1 -0
  44. package/dist/topology.d.ts +11 -0
  45. package/dist/topology.d.ts.map +1 -0
  46. package/dist/topology.js +237 -0
  47. package/dist/topology.js.map +1 -0
  48. package/dist/types.d.ts +110 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +11 -0
  51. package/dist/types.js.map +1 -0
  52. package/package.json +69 -0
  53. package/src/assessment.ts +98 -0
  54. package/src/cli.ts +74 -0
  55. package/src/index.ts +39 -0
  56. package/src/ontology/compatibility.ts +132 -0
  57. package/src/ontology/define.ts +35 -0
  58. package/src/ontology/index.ts +29 -0
  59. package/src/ontology/normalize.ts +63 -0
  60. package/src/ontology/snapshot.ts +324 -0
  61. package/src/ontology/types.ts +156 -0
  62. package/src/ontology/validate.ts +208 -0
  63. package/src/topology.ts +211 -0
  64. package/src/types.ts +142 -0
@@ -0,0 +1,123 @@
1
+ /** A scalar representation supplied by this package. */
2
+ export type PrimitiveValueType = "string" | "number" | "integer" | "boolean" | "date" | "datetime" | "json";
3
+ /** A product-defined scalar type built on one primitive representation. */
4
+ export interface ValueTypeDefinition {
5
+ id: string;
6
+ primitive: PrimitiveValueType;
7
+ label?: string;
8
+ }
9
+ /** A closed set of product-defined string values. */
10
+ export interface VocabularyDefinition {
11
+ id: string;
12
+ values: readonly string[];
13
+ label?: string;
14
+ }
15
+ /** A field's representation: a primitive or the id of a value type or vocabulary. */
16
+ export type FieldValueType = PrimitiveValueType | string;
17
+ /** One stable field on a domain type or relation. */
18
+ export interface FieldDefinition {
19
+ id: string;
20
+ valueType: FieldValueType;
21
+ required?: boolean;
22
+ label?: string;
23
+ }
24
+ /** A product-owned domain type with stable fields. */
25
+ export interface DomainTypeDefinition {
26
+ id: string;
27
+ fields?: readonly FieldDefinition[];
28
+ label?: string;
29
+ }
30
+ /** The allowed cardinality of a directed relation. */
31
+ export type RelationCardinality = "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many";
32
+ /** A directed relation that may carry its own stable fields. */
33
+ export interface RelationDefinition {
34
+ id: string;
35
+ from: string;
36
+ to: string;
37
+ cardinality: RelationCardinality;
38
+ properties?: readonly FieldDefinition[];
39
+ label?: string;
40
+ }
41
+ /** A consumer-owned semantic model. `schemaVersion` belongs to that consumer, not this package. */
42
+ export interface DomainModelDefinition {
43
+ id: string;
44
+ schemaVersion: string;
45
+ valueTypes?: readonly ValueTypeDefinition[];
46
+ vocabularies?: readonly VocabularyDefinition[];
47
+ types?: readonly DomainTypeDefinition[];
48
+ relations?: readonly RelationDefinition[];
49
+ }
50
+ /** A normalized, immutable-looking model value returned by `defineDomainModel`. */
51
+ export interface DomainModel extends DomainModelDefinition {
52
+ valueTypes: readonly ValueTypeDefinition[];
53
+ vocabularies: readonly VocabularyDefinition[];
54
+ types: readonly DomainTypeDefinition[];
55
+ relations: readonly RelationDefinition[];
56
+ }
57
+ /** One observed issue; callers decide whether an error blocks their workflow. */
58
+ export interface DomainModelFinding {
59
+ rule: string;
60
+ severity: "error" | "warning";
61
+ message: string;
62
+ path?: string;
63
+ }
64
+ /** A change detected between two model versions. */
65
+ export interface DomainModelChange {
66
+ kind: "additive" | "breaking";
67
+ subject: "model" | "value-type" | "vocabulary" | "vocabulary-value" | "type" | "field" | "relation" | "relation-property";
68
+ id: string;
69
+ message: string;
70
+ }
71
+ /** Machine-readable outcome of `compareDomainModels`. */
72
+ export interface DomainModelCompatibilityReport {
73
+ compatible: boolean;
74
+ changes: readonly DomainModelChange[];
75
+ previousFindings: readonly DomainModelFinding[];
76
+ nextFindings: readonly DomainModelFinding[];
77
+ }
78
+ /** One product-owned record captured against a declared domain type. */
79
+ export interface DomainRecordDefinition {
80
+ id: string;
81
+ type: string;
82
+ values?: Readonly<Record<string, unknown>>;
83
+ }
84
+ /** A detached record with an explicit value object. */
85
+ export interface DomainRecord {
86
+ id: string;
87
+ type: string;
88
+ values: Readonly<Record<string, unknown>>;
89
+ }
90
+ /** One directed product-owned relation captured against a declared relation type. */
91
+ export interface DomainRelationDefinition {
92
+ type: string;
93
+ from: string;
94
+ to: string;
95
+ values?: Readonly<Record<string, unknown>>;
96
+ }
97
+ /** A detached directed relation with an explicit value object. */
98
+ export interface DomainRelation {
99
+ type: string;
100
+ from: string;
101
+ to: string;
102
+ values: Readonly<Record<string, unknown>>;
103
+ }
104
+ /** Authoring input for a product-owned collection of records and relations. */
105
+ export interface DomainSnapshotDefinition {
106
+ records?: readonly DomainRecordDefinition[];
107
+ relations?: readonly DomainRelationDefinition[];
108
+ }
109
+ /** A detached domain snapshot with explicit collections. */
110
+ export interface DomainSnapshot {
111
+ records: readonly DomainRecord[];
112
+ relations: readonly DomainRelation[];
113
+ }
114
+ /** One observed issue in a snapshot; callers decide whether an error blocks their workflow. */
115
+ export interface DomainSnapshotFinding {
116
+ rule: string;
117
+ severity: "error" | "warning";
118
+ message: string;
119
+ path?: string;
120
+ }
121
+ export declare const PRIMITIVE_VALUE_TYPES: readonly PrimitiveValueType[];
122
+ export declare const RELATION_CARDINALITIES: readonly RelationCardinality[];
123
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ontology/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export const PRIMITIVE_VALUE_TYPES = [
2
+ "string",
3
+ "number",
4
+ "integer",
5
+ "boolean",
6
+ "date",
7
+ "datetime",
8
+ "json",
9
+ ];
10
+ export const RELATION_CARDINALITIES = [
11
+ "one-to-one",
12
+ "one-to-many",
13
+ "many-to-one",
14
+ "many-to-many",
15
+ ];
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/ontology/types.ts"],"names":[],"mappings":"AA4IA,MAAM,CAAC,MAAM,qBAAqB,GAAkC;IAClE,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,SAAS;IACT,MAAM;IACN,UAAU;IACV,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAmC;IACpE,YAAY;IACZ,aAAa;IACb,aAAa;IACb,cAAc;CACf,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { DomainModelFinding } from "./types.js";
2
+ /**
3
+ * Validates real definitions rather than a consumer-provided success flag.
4
+ * It never throws for malformed input and reports every independently
5
+ * checkable structural and referential finding.
6
+ */
7
+ export declare function validateDomainModel(value: unknown): DomainModelFinding[];
8
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/ontology/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,YAAY,CAAC;AAqGtE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAoGxE"}
@@ -0,0 +1,190 @@
1
+ import { PRIMITIVE_VALUE_TYPES, RELATION_CARDINALITIES } from "./types.js";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function finding(rule, message, path) {
6
+ return { rule, severity: "error", message, path };
7
+ }
8
+ function arrayAt(record, key, findings, path) {
9
+ const value = record[key];
10
+ if (value === undefined)
11
+ return [];
12
+ if (Array.isArray(value))
13
+ return value;
14
+ findings.push(finding("collection-shape", `${key} must be an array when provided.`, path));
15
+ return [];
16
+ }
17
+ function stringAt(record, key, findings, path) {
18
+ const value = record[key];
19
+ if (typeof value === "string" && value.length > 0)
20
+ return value;
21
+ findings.push(finding("required-string", `${key} must be a non-empty string.`, path));
22
+ return undefined;
23
+ }
24
+ function optionalLabel(record, findings, path) {
25
+ if (record.label !== undefined && typeof record.label !== "string") {
26
+ findings.push(finding("label-shape", "label must be a string when provided.", path));
27
+ }
28
+ }
29
+ function isPrimitive(value) {
30
+ return typeof value === "string" && PRIMITIVE_VALUE_TYPES.includes(value);
31
+ }
32
+ function isCardinality(value) {
33
+ return typeof value === "string" && RELATION_CARDINALITIES.includes(value);
34
+ }
35
+ function hasNamespace(id, modelId) {
36
+ return id.startsWith(`${modelId}.`) && id.length > modelId.length + 1;
37
+ }
38
+ function isStableId(id) {
39
+ return /^[a-z][a-z0-9-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)+$/.test(id);
40
+ }
41
+ function validateStableId(id, modelId, findings, path, expectedPrefix) {
42
+ if (id === undefined)
43
+ return;
44
+ if (!isStableId(id)) {
45
+ findings.push(finding("stable-id-shape", "id must be a dotted, namespaced identifier.", path));
46
+ }
47
+ if (modelId !== undefined && !hasNamespace(id, modelId)) {
48
+ findings.push(finding("stable-id-namespace", `id must begin with "${modelId}.".`, path));
49
+ }
50
+ if (expectedPrefix !== undefined && !id.startsWith(`${expectedPrefix}.`)) {
51
+ findings.push(finding("field-parent", `id must begin with "${expectedPrefix}.".`, path));
52
+ }
53
+ }
54
+ function validateFields(fields, fieldName, ownerId, modelId, knownValueIds, findings, path) {
55
+ const ids = new Set();
56
+ fields.forEach((candidate, index) => {
57
+ const fieldPath = `${path}.${fieldName}[${index}]`;
58
+ if (!isRecord(candidate)) {
59
+ findings.push(finding("field-shape", "A field must be an object.", fieldPath));
60
+ return;
61
+ }
62
+ const id = stringAt(candidate, "id", findings, `${fieldPath}.id`);
63
+ validateStableId(id, modelId, findings, `${fieldPath}.id`, ownerId);
64
+ if (id !== undefined) {
65
+ if (ids.has(id))
66
+ findings.push(finding("duplicate-field-id", `Duplicate field id "${id}".`, `${fieldPath}.id`));
67
+ ids.add(id);
68
+ }
69
+ const valueType = stringAt(candidate, "valueType", findings, `${fieldPath}.valueType`);
70
+ if (valueType !== undefined && !isPrimitive(valueType) && !knownValueIds.has(valueType)) {
71
+ findings.push(finding("unknown-value-type", `valueType "${valueType}" does not reference a primitive, value type, or vocabulary.`, `${fieldPath}.valueType`));
72
+ }
73
+ if (candidate.required !== undefined && typeof candidate.required !== "boolean") {
74
+ findings.push(finding("required-shape", "required must be a boolean when provided.", `${fieldPath}.required`));
75
+ }
76
+ optionalLabel(candidate, findings, `${fieldPath}.label`);
77
+ });
78
+ }
79
+ /**
80
+ * Validates real definitions rather than a consumer-provided success flag.
81
+ * It never throws for malformed input and reports every independently
82
+ * checkable structural and referential finding.
83
+ */
84
+ export function validateDomainModel(value) {
85
+ const findings = [];
86
+ if (!isRecord(value))
87
+ return [finding("model-shape", "A domain model must be an object.", "$")];
88
+ const modelId = stringAt(value, "id", findings, "id");
89
+ if (modelId !== undefined && !/^[a-z][a-z0-9-]*$/.test(modelId)) {
90
+ findings.push(finding("model-id-shape", "id must be a lowercase namespace token.", "id"));
91
+ }
92
+ stringAt(value, "schemaVersion", findings, "schemaVersion");
93
+ const valueTypes = arrayAt(value, "valueTypes", findings, "valueTypes");
94
+ const vocabularies = arrayAt(value, "vocabularies", findings, "vocabularies");
95
+ const types = arrayAt(value, "types", findings, "types");
96
+ const relations = arrayAt(value, "relations", findings, "relations");
97
+ const knownValueIds = new Set();
98
+ const allNamedIds = new Set();
99
+ const recordNamed = (id, kind, path, isValueReference = false) => {
100
+ if (id === undefined)
101
+ return;
102
+ if (allNamedIds.has(id))
103
+ findings.push(finding("duplicate-id", `Duplicate ${kind} id "${id}".`, path));
104
+ allNamedIds.add(id);
105
+ if (isValueReference)
106
+ knownValueIds.add(id);
107
+ };
108
+ valueTypes.forEach((candidate, index) => {
109
+ const path = `valueTypes[${index}]`;
110
+ if (!isRecord(candidate)) {
111
+ findings.push(finding("value-type-shape", "A value type must be an object.", path));
112
+ return;
113
+ }
114
+ const id = stringAt(candidate, "id", findings, `${path}.id`);
115
+ validateStableId(id, modelId, findings, `${path}.id`);
116
+ recordNamed(id, "value type", `${path}.id`, true);
117
+ if (!isPrimitive(candidate.primitive))
118
+ findings.push(finding("primitive-known", "primitive must be a known primitive value type.", `${path}.primitive`));
119
+ optionalLabel(candidate, findings, `${path}.label`);
120
+ });
121
+ vocabularies.forEach((candidate, index) => {
122
+ const path = `vocabularies[${index}]`;
123
+ if (!isRecord(candidate)) {
124
+ findings.push(finding("vocabulary-shape", "A vocabulary must be an object.", path));
125
+ return;
126
+ }
127
+ const id = stringAt(candidate, "id", findings, `${path}.id`);
128
+ validateStableId(id, modelId, findings, `${path}.id`);
129
+ recordNamed(id, "vocabulary", `${path}.id`, true);
130
+ const values = arrayAt(candidate, "values", findings, `${path}.values`);
131
+ const seen = new Set();
132
+ values.forEach((entry, valueIndex) => {
133
+ if (typeof entry !== "string" || entry.length === 0) {
134
+ findings.push(finding("vocabulary-value-shape", "Vocabulary values must be non-empty strings.", `${path}.values[${valueIndex}]`));
135
+ }
136
+ else if (seen.has(entry)) {
137
+ findings.push(finding("duplicate-vocabulary-value", `Duplicate vocabulary value "${entry}".`, `${path}.values[${valueIndex}]`));
138
+ }
139
+ else
140
+ seen.add(entry);
141
+ });
142
+ optionalLabel(candidate, findings, `${path}.label`);
143
+ });
144
+ const typeIds = new Set();
145
+ types.forEach((candidate, index) => {
146
+ const path = `types[${index}]`;
147
+ if (!isRecord(candidate)) {
148
+ findings.push(finding("type-shape", "A domain type must be an object.", path));
149
+ return;
150
+ }
151
+ const id = stringAt(candidate, "id", findings, `${path}.id`);
152
+ validateStableId(id, modelId, findings, `${path}.id`);
153
+ if (id !== undefined) {
154
+ if (typeIds.has(id))
155
+ findings.push(finding("duplicate-type-id", `Duplicate type id "${id}".`, `${path}.id`));
156
+ typeIds.add(id);
157
+ }
158
+ recordNamed(id, "type", `${path}.id`);
159
+ validateFields(arrayAt(candidate, "fields", findings, `${path}.fields`), "fields", id, modelId, knownValueIds, findings, path);
160
+ optionalLabel(candidate, findings, `${path}.label`);
161
+ });
162
+ const relationIds = new Set();
163
+ relations.forEach((candidate, index) => {
164
+ const path = `relations[${index}]`;
165
+ if (!isRecord(candidate)) {
166
+ findings.push(finding("relation-shape", "A relation must be an object.", path));
167
+ return;
168
+ }
169
+ const id = stringAt(candidate, "id", findings, `${path}.id`);
170
+ validateStableId(id, modelId, findings, `${path}.id`);
171
+ if (id !== undefined) {
172
+ if (relationIds.has(id))
173
+ findings.push(finding("duplicate-relation-id", `Duplicate relation id "${id}".`, `${path}.id`));
174
+ relationIds.add(id);
175
+ }
176
+ recordNamed(id, "relation", `${path}.id`);
177
+ const from = stringAt(candidate, "from", findings, `${path}.from`);
178
+ const to = stringAt(candidate, "to", findings, `${path}.to`);
179
+ if (from !== undefined && !typeIds.has(from))
180
+ findings.push(finding("relation-from-known", `from endpoint "${from}" does not reference a type.`, `${path}.from`));
181
+ if (to !== undefined && !typeIds.has(to))
182
+ findings.push(finding("relation-to-known", `to endpoint "${to}" does not reference a type.`, `${path}.to`));
183
+ if (!isCardinality(candidate.cardinality))
184
+ findings.push(finding("relation-cardinality-known", "cardinality must be a supported relation cardinality.", `${path}.cardinality`));
185
+ validateFields(arrayAt(candidate, "properties", findings, `${path}.properties`), "properties", id, modelId, knownValueIds, findings, path);
186
+ optionalLabel(candidate, findings, `${path}.label`);
187
+ });
188
+ return findings;
189
+ }
190
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/ontology/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type ArchitectureFinding, type OperatingTopology, type OperatingTopologyCompatibilityReport, type OperatingTopologyDefinition } from "./types.js";
2
+ /** Creates a detached topology definition without asserting that it is valid. */
3
+ export declare function defineOperatingTopology(definition: OperatingTopologyDefinition): OperatingTopology;
4
+ /** Validates shape, identities, references, ownership and declared interfaces without I/O. */
5
+ export declare function validateOperatingTopology(value: unknown): ArchitectureFinding[];
6
+ /** Returns one canonical property layout and ordering. */
7
+ export declare function normalizeOperatingTopology(definition: OperatingTopologyDefinition): OperatingTopology;
8
+ export declare function serializeOperatingTopology(definition: OperatingTopologyDefinition): string;
9
+ /** Compares stable architectural contracts. Description and schemaVersion are not compatibility surface. */
10
+ export declare function compareOperatingTopologies(previous: OperatingTopologyDefinition, next: OperatingTopologyDefinition): OperatingTopologyCompatibilityReport;
11
+ //# sourceMappingURL=topology.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topology.d.ts","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,mBAAmB,EAIxB,KAAK,iBAAiB,EACtB,KAAK,oCAAoC,EACzC,KAAK,2BAA2B,EAEjC,MAAM,YAAY,CAAC;AA2CpB,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,2BAA2B,GAAG,iBAAiB,CASlG;AAED,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAkF/E;AAID,0DAA0D;AAC1D,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,2BAA2B,GAAG,iBAAiB,CAgBrG;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,2BAA2B,GAAG,MAAM,CAE1F;AAID,4GAA4G;AAC5G,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,2BAA2B,EAAE,IAAI,EAAE,2BAA2B,GAAG,oCAAoC,CA8BzJ"}
@@ -0,0 +1,237 @@
1
+ import { OPERATING_RESPONSIBILITIES, OPERATING_SCOPE_KINDS, OPERATING_SYSTEM_KINDS, } from "./types.js";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function finding(rule, message, path) {
6
+ return { rule, severity: "error", message, path };
7
+ }
8
+ function requiredString(value, key, path, findings) {
9
+ if (!isRecord(value) || typeof value[key] !== "string" || value[key].length === 0) {
10
+ findings.push(finding("required-string", `${key} must be a non-empty string.`, path));
11
+ return undefined;
12
+ }
13
+ return value[key];
14
+ }
15
+ function arrayAt(value, key, path, findings) {
16
+ if (value[key] === undefined)
17
+ return [];
18
+ if (Array.isArray(value[key]))
19
+ return value[key];
20
+ findings.push(finding("collection-shape", `${key} must be an array when provided.`, path));
21
+ return [];
22
+ }
23
+ function uniqueStrings(value, path, findings) {
24
+ if (!Array.isArray(value) || value.length === 0) {
25
+ findings.push(finding("responsibilities-shape", "responsibilities must be a non-empty array.", path));
26
+ return [];
27
+ }
28
+ const result = [];
29
+ for (const [index, item] of value.entries()) {
30
+ if (typeof item !== "string" || !OPERATING_RESPONSIBILITIES.includes(item)) {
31
+ findings.push(finding("responsibility-known", "responsibility must be a supported value.", `${path}[${index}]`));
32
+ }
33
+ else if (result.includes(item)) {
34
+ findings.push(finding("duplicate-responsibility", `Duplicate responsibility "${item}".`, `${path}[${index}]`));
35
+ }
36
+ else
37
+ result.push(item);
38
+ }
39
+ return result;
40
+ }
41
+ /** Creates a detached topology definition without asserting that it is valid. */
42
+ export function defineOperatingTopology(definition) {
43
+ return {
44
+ id: definition.id,
45
+ schemaVersion: definition.schemaVersion,
46
+ scope: { ...definition.scope },
47
+ systems: (definition.systems ?? []).map((system) => ({ ...system, responsibilities: [...system.responsibilities] })),
48
+ authorities: (definition.authorities ?? []).map((authority) => ({ ...authority })),
49
+ interfaces: (definition.interfaces ?? []).map((entry) => ({ ...entry, responsibilities: [...entry.responsibilities] })),
50
+ };
51
+ }
52
+ /** Validates shape, identities, references, ownership and declared interfaces without I/O. */
53
+ export function validateOperatingTopology(value) {
54
+ const findings = [];
55
+ if (!isRecord(value))
56
+ return [finding("topology-shape", "An operating topology must be an object.", "$")];
57
+ const topologyId = requiredString(value, "id", "id", findings);
58
+ if (topologyId !== undefined && !/^[a-z][a-z0-9-]*$/.test(topologyId))
59
+ findings.push(finding("topology-id-shape", "id must be a lowercase namespace token.", "id"));
60
+ requiredString(value, "schemaVersion", "schemaVersion", findings);
61
+ const scope = value.scope;
62
+ if (!isRecord(scope))
63
+ findings.push(finding("scope-shape", "scope must be an object.", "scope"));
64
+ else {
65
+ requiredString(scope, "id", "scope.id", findings);
66
+ const kind = requiredString(scope, "kind", "scope.kind", findings);
67
+ if (kind !== undefined && !OPERATING_SCOPE_KINDS.includes(kind))
68
+ findings.push(finding("scope-kind-known", "scope.kind must be portfolio or business.", "scope.kind"));
69
+ }
70
+ const systems = arrayAt(value, "systems", "systems", findings);
71
+ const systemIds = new Set();
72
+ const systemResponsibilities = new Map();
73
+ const responsibilityCoverage = new Set();
74
+ for (const [index, candidate] of systems.entries()) {
75
+ const path = `systems[${index}]`;
76
+ if (!isRecord(candidate)) {
77
+ findings.push(finding("system-shape", "A system must be an object.", path));
78
+ continue;
79
+ }
80
+ const id = requiredString(candidate, "id", `${path}.id`, findings);
81
+ if (id !== undefined) {
82
+ if (systemIds.has(id))
83
+ findings.push(finding("duplicate-system-id", `Duplicate system id "${id}".`, `${path}.id`));
84
+ systemIds.add(id);
85
+ }
86
+ const kind = requiredString(candidate, "kind", `${path}.kind`, findings);
87
+ if (kind !== undefined && !OPERATING_SYSTEM_KINDS.includes(kind))
88
+ findings.push(finding("system-kind-known", "kind must be a supported system kind.", `${path}.kind`));
89
+ const responsibilities = uniqueStrings(candidate.responsibilities, `${path}.responsibilities`, findings);
90
+ for (const responsibility of responsibilities)
91
+ responsibilityCoverage.add(responsibility);
92
+ if (id !== undefined && !systemResponsibilities.has(id))
93
+ systemResponsibilities.set(id, new Set(responsibilities));
94
+ for (const optional of ["provider", "locator"])
95
+ if (candidate[optional] !== undefined && typeof candidate[optional] !== "string")
96
+ findings.push(finding(`${optional}-shape`, `${optional} must be a string when provided.`, `${path}.${optional}`));
97
+ if (candidate.visibility !== undefined && !["public", "private", "restricted"].includes(candidate.visibility))
98
+ findings.push(finding("visibility-known", "visibility must be public, private, or restricted.", `${path}.visibility`));
99
+ }
100
+ if (systems.length === 0)
101
+ findings.push(finding("systems-required", "At least one system is required.", "systems"));
102
+ if (!responsibilityCoverage.has("control-plane"))
103
+ findings.push(finding("control-plane-required", "At least one system must implement the control-plane responsibility.", "systems"));
104
+ const authorities = arrayAt(value, "authorities", "authorities", findings);
105
+ if (authorities.length === 0)
106
+ findings.push(finding("authorities-required", "At least one authority is required.", "authorities"));
107
+ const authorityResponsibilities = new Set();
108
+ for (const [index, candidate] of authorities.entries()) {
109
+ const path = `authorities[${index}]`;
110
+ if (!isRecord(candidate)) {
111
+ findings.push(finding("authority-shape", "An authority must be an object.", path));
112
+ continue;
113
+ }
114
+ const responsibility = requiredString(candidate, "responsibility", `${path}.responsibility`, findings);
115
+ if (responsibility !== undefined) {
116
+ if (!OPERATING_RESPONSIBILITIES.includes(responsibility))
117
+ findings.push(finding("responsibility-known", "responsibility must be a supported value.", `${path}.responsibility`));
118
+ if (!responsibilityCoverage.has(responsibility))
119
+ findings.push(finding("responsibility-unimplemented", `No system implements responsibility "${responsibility}".`, `${path}.responsibility`));
120
+ if (authorityResponsibilities.has(responsibility))
121
+ findings.push(finding("duplicate-authority", `Responsibility "${responsibility}" has more than one authority.`, `${path}.responsibility`));
122
+ authorityResponsibilities.add(responsibility);
123
+ }
124
+ requiredString(candidate, "owner", `${path}.owner`, findings);
125
+ const record = requiredString(candidate, "systemOfRecord", `${path}.systemOfRecord`, findings);
126
+ if (record !== undefined && !systemIds.has(record))
127
+ findings.push(finding("system-of-record-known", `systemOfRecord "${record}" is not a declared system.`, `${path}.systemOfRecord`));
128
+ else if (record !== undefined && responsibility !== undefined && OPERATING_RESPONSIBILITIES.includes(responsibility) && !systemResponsibilities.get(record)?.has(responsibility)) {
129
+ findings.push(finding("system-of-record-responsibility", `systemOfRecord "${record}" does not implement responsibility "${responsibility}".`, `${path}.systemOfRecord`));
130
+ }
131
+ }
132
+ for (const responsibility of responsibilityCoverage)
133
+ if (!authorityResponsibilities.has(responsibility))
134
+ findings.push(finding("authority-required", `Responsibility "${responsibility}" needs exactly one authority.`, "authorities"));
135
+ const interfaces = arrayAt(value, "interfaces", "interfaces", findings);
136
+ const interfaceIds = new Set();
137
+ for (const [index, candidate] of interfaces.entries()) {
138
+ const path = `interfaces[${index}]`;
139
+ if (!isRecord(candidate)) {
140
+ findings.push(finding("interface-shape", "An interface must be an object.", path));
141
+ continue;
142
+ }
143
+ const id = requiredString(candidate, "id", `${path}.id`, findings);
144
+ if (id !== undefined) {
145
+ if (interfaceIds.has(id))
146
+ findings.push(finding("duplicate-interface-id", `Duplicate interface id "${id}".`, `${path}.id`));
147
+ interfaceIds.add(id);
148
+ }
149
+ for (const endpoint of ["from", "to"]) {
150
+ const system = requiredString(candidate, endpoint, `${path}.${endpoint}`, findings);
151
+ if (system !== undefined && !systemIds.has(system))
152
+ findings.push(finding("interface-system-known", `${endpoint} system "${system}" is not declared.`, `${path}.${endpoint}`));
153
+ }
154
+ if (candidate.from !== undefined && candidate.from === candidate.to)
155
+ findings.push(finding("interface-boundary", "An interface must cross two different systems.", path));
156
+ const responsibilities = uniqueStrings(candidate.responsibilities, `${path}.responsibilities`, findings);
157
+ for (const responsibility of responsibilities) {
158
+ if (!responsibilityCoverage.has(responsibility))
159
+ findings.push(finding("interface-responsibility-unimplemented", `No system implements interface responsibility "${responsibility}".`, `${path}.responsibilities`));
160
+ }
161
+ if (candidate.description !== undefined && typeof candidate.description !== "string")
162
+ findings.push(finding("description-shape", "description must be a string when provided.", `${path}.description`));
163
+ }
164
+ return findings;
165
+ }
166
+ function byId(left, right) { return left.id.localeCompare(right.id); }
167
+ /** Returns one canonical property layout and ordering. */
168
+ export function normalizeOperatingTopology(definition) {
169
+ const value = defineOperatingTopology(definition);
170
+ const systems = value.systems.map((system) => {
171
+ const next = { id: system.id, kind: system.kind, responsibilities: [...system.responsibilities].sort() };
172
+ if (system.provider !== undefined)
173
+ next.provider = system.provider;
174
+ if (system.locator !== undefined)
175
+ next.locator = system.locator;
176
+ if (system.visibility !== undefined)
177
+ next.visibility = system.visibility;
178
+ return next;
179
+ });
180
+ const authorities = value.authorities.map((entry) => ({ responsibility: entry.responsibility, owner: entry.owner, systemOfRecord: entry.systemOfRecord }));
181
+ const interfaces = value.interfaces.map((entry) => {
182
+ const next = { id: entry.id, from: entry.from, to: entry.to, responsibilities: [...entry.responsibilities].sort() };
183
+ if (entry.description !== undefined)
184
+ next.description = entry.description;
185
+ return next;
186
+ });
187
+ return { id: value.id, schemaVersion: value.schemaVersion, scope: { id: value.scope.id, kind: value.scope.kind }, systems: systems.sort(byId), authorities: authorities.sort((a, b) => a.responsibility.localeCompare(b.responsibility)), interfaces: interfaces.sort(byId) };
188
+ }
189
+ export function serializeOperatingTopology(definition) {
190
+ return `${JSON.stringify(normalizeOperatingTopology(definition), null, 2)}\n`;
191
+ }
192
+ function mapById(values) { return new Map(values.map((value) => [value.id, value])); }
193
+ /** Compares stable architectural contracts. Description and schemaVersion are not compatibility surface. */
194
+ export function compareOperatingTopologies(previous, next) {
195
+ const previousFindings = validateOperatingTopology(previous);
196
+ const nextFindings = validateOperatingTopology(next);
197
+ if (previousFindings.some((entry) => entry.severity === "error") || nextFindings.some((entry) => entry.severity === "error"))
198
+ return { compatible: false, changes: [], previousFindings, nextFindings };
199
+ const before = normalizeOperatingTopology(previous);
200
+ const after = normalizeOperatingTopology(next);
201
+ const changes = [];
202
+ if (before.id !== after.id)
203
+ changes.push({ kind: "breaking", subject: "topology", id: after.id, message: `Topology id changed from "${before.id}" to "${after.id}".` });
204
+ if (JSON.stringify(before.scope) !== JSON.stringify(after.scope))
205
+ changes.push({ kind: "breaking", subject: "scope", id: after.scope.id, message: "Operating scope changed." });
206
+ const compareCollection = (subject, left, right, contract) => {
207
+ const oldValues = mapById(left);
208
+ const newValues = mapById(right);
209
+ for (const [id, value] of oldValues) {
210
+ const replacement = newValues.get(id);
211
+ if (replacement === undefined)
212
+ changes.push({ kind: "breaking", subject, id, message: `${subject} "${id}" was removed.` });
213
+ else if (JSON.stringify(contract(value)) !== JSON.stringify(contract(replacement)))
214
+ changes.push({ kind: "breaking", subject, id, message: `${subject} "${id}" changed contract.` });
215
+ }
216
+ for (const [id] of newValues)
217
+ if (!oldValues.has(id))
218
+ changes.push({ kind: "additive", subject, id, message: `${subject} "${id}" was added.` });
219
+ };
220
+ compareCollection("system", before.systems, after.systems, (entry) => entry);
221
+ compareCollection("interface", before.interfaces, after.interfaces, (entry) => ({ id: entry.id, from: entry.from, to: entry.to, responsibilities: entry.responsibilities }));
222
+ const oldAuthorities = new Map(before.authorities.map((entry) => [entry.responsibility, entry]));
223
+ const newAuthorities = new Map(after.authorities.map((entry) => [entry.responsibility, entry]));
224
+ for (const [id, value] of oldAuthorities) {
225
+ const replacement = newAuthorities.get(id);
226
+ if (replacement === undefined)
227
+ changes.push({ kind: "breaking", subject: "authority", id, message: `authority "${id}" was removed.` });
228
+ else if (JSON.stringify(value) !== JSON.stringify(replacement))
229
+ changes.push({ kind: "breaking", subject: "authority", id, message: `authority "${id}" changed contract.` });
230
+ }
231
+ for (const [id] of newAuthorities)
232
+ if (!oldAuthorities.has(id))
233
+ changes.push({ kind: "additive", subject: "authority", id, message: `authority "${id}" was added.` });
234
+ changes.sort((a, b) => `${a.subject}:${a.id}:${a.kind}`.localeCompare(`${b.subject}:${b.id}:${b.kind}`));
235
+ return { compatible: changes.every((entry) => entry.kind !== "breaking"), changes, previousFindings, nextFindings };
236
+ }
237
+ //# sourceMappingURL=topology.js.map