@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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/assessment.d.ts +9 -0
- package/dist/assessment.d.ts.map +1 -0
- package/dist/assessment.js +98 -0
- package/dist/assessment.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +96 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/ontology/compatibility.d.ts +7 -0
- package/dist/ontology/compatibility.d.ts.map +1 -0
- package/dist/ontology/compatibility.js +135 -0
- package/dist/ontology/compatibility.js.map +1 -0
- package/dist/ontology/define.d.ts +8 -0
- package/dist/ontology/define.d.ts.map +1 -0
- package/dist/ontology/define.js +30 -0
- package/dist/ontology/define.js.map +1 -0
- package/dist/ontology/index.d.ts +9 -0
- package/dist/ontology/index.d.ts.map +1 -0
- package/dist/ontology/index.js +8 -0
- package/dist/ontology/index.js.map +1 -0
- package/dist/ontology/normalize.d.ts +6 -0
- package/dist/ontology/normalize.d.ts.map +1 -0
- package/dist/ontology/normalize.js +60 -0
- package/dist/ontology/normalize.js.map +1 -0
- package/dist/ontology/snapshot.d.ts +14 -0
- package/dist/ontology/snapshot.d.ts.map +1 -0
- package/dist/ontology/snapshot.js +299 -0
- package/dist/ontology/snapshot.js.map +1 -0
- package/dist/ontology/types.d.ts +123 -0
- package/dist/ontology/types.d.ts.map +1 -0
- package/dist/ontology/types.js +16 -0
- package/dist/ontology/types.js.map +1 -0
- package/dist/ontology/validate.d.ts +8 -0
- package/dist/ontology/validate.d.ts.map +1 -0
- package/dist/ontology/validate.js +190 -0
- package/dist/ontology/validate.js.map +1 -0
- package/dist/topology.d.ts +11 -0
- package/dist/topology.d.ts.map +1 -0
- package/dist/topology.js +237 -0
- package/dist/topology.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +69 -0
- package/src/assessment.ts +98 -0
- package/src/cli.ts +74 -0
- package/src/index.ts +39 -0
- package/src/ontology/compatibility.ts +132 -0
- package/src/ontology/define.ts +35 -0
- package/src/ontology/index.ts +29 -0
- package/src/ontology/normalize.ts +63 -0
- package/src/ontology/snapshot.ts +324 -0
- package/src/ontology/types.ts +156 -0
- package/src/ontology/validate.ts +208 -0
- package/src/topology.ts +211 -0
- package/src/types.ts +142 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.2] - 2026-08-30
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Updated the package's public repository, issue-tracker, and homepage metadata to the canonical Foundry repository. This change is not a publication or qualification claim.
|
|
13
|
+
|
|
14
|
+
## [0.1.1] - 2026-08-24
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Corrected the install guidance now that Architect is a public package.
|
|
19
|
+
|
|
20
|
+
## [0.1.0] - 2026-08-23
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Initial architect role contract for provider-neutral scopes, systems,
|
|
25
|
+
responsibilities, authorities, systems of record, and interfaces.
|
|
26
|
+
- Deterministic topology validation, normalization, serialization, and
|
|
27
|
+
compatibility comparison.
|
|
28
|
+
- Evidence-based architecture exception assessment with explicit indeterminate
|
|
29
|
+
results when no material changes have been observed.
|
|
30
|
+
- `architect-check` topology and exception-assessment commands.
|
|
31
|
+
- Ontology model and snapshot API under `@vespeneventures/architect/ontology`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Calvin Hung
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @clossys/architect
|
|
2
|
+
|
|
3
|
+
The architect role for provider-neutral operating architecture. It answers:
|
|
4
|
+
|
|
5
|
+
> Do the business ontology, operating boundaries, authoritative systems, and
|
|
6
|
+
> interfaces match how material work actually crosses the business?
|
|
7
|
+
|
|
8
|
+
The package defines business and portfolio scopes, systems, responsibilities,
|
|
9
|
+
authoritative ownership, systems of record, and directional interfaces. It
|
|
10
|
+
then assesses independent change observations against that declared topology.
|
|
11
|
+
It performs no network I/O and has no runtime dependencies. Install it with:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @clossys/architect
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Job charter
|
|
18
|
+
|
|
19
|
+
Architect operates in **optimize** mode. Its durable objective is to reduce
|
|
20
|
+
architecture exception rate while respecting consumer-owned security,
|
|
21
|
+
privacy, authority, and lifecycle guardrails:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
architecture exception rate
|
|
25
|
+
= observed material changes with at least one undeclared boundary crossing
|
|
26
|
+
/ all observed material changes
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No observed material changes means the rate is **indeterminate**, never zero.
|
|
30
|
+
The role senses actual changes, judges them against declared boundaries,
|
|
31
|
+
proposes architecture changes, verifies subsequent evidence, and learns or
|
|
32
|
+
escalates. It does not transfer repositories, mutate GitHub, create systems,
|
|
33
|
+
or grant itself authority. A consumer approves changes; an authorized builder
|
|
34
|
+
may materialize them.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
assessArchitectureExceptions,
|
|
41
|
+
defineOperatingTopology,
|
|
42
|
+
serializeOperatingTopology,
|
|
43
|
+
validateOperatingTopology,
|
|
44
|
+
} from "@clossys/architect";
|
|
45
|
+
|
|
46
|
+
const topology = defineOperatingTopology({
|
|
47
|
+
id: "example",
|
|
48
|
+
schemaVersion: "0.1.0",
|
|
49
|
+
scope: { id: "example-business", kind: "business" },
|
|
50
|
+
systems: [
|
|
51
|
+
{ id: "workspace", kind: "workspace", responsibilities: ["control-plane"], visibility: "private" },
|
|
52
|
+
{ id: "product", kind: "repository", responsibilities: ["product"], visibility: "private" },
|
|
53
|
+
],
|
|
54
|
+
authorities: [
|
|
55
|
+
{ responsibility: "control-plane", owner: "business-owner", systemOfRecord: "workspace" },
|
|
56
|
+
{ responsibility: "product", owner: "product-owner", systemOfRecord: "product" },
|
|
57
|
+
],
|
|
58
|
+
interfaces: [
|
|
59
|
+
{ id: "workspace-to-product", from: "workspace", to: "product", responsibilities: ["product"] },
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (validateOperatingTopology(topology).length > 0) throw new Error("Invalid topology");
|
|
64
|
+
const artifact = serializeOperatingTopology(topology);
|
|
65
|
+
|
|
66
|
+
const assessment = assessArchitectureExceptions(
|
|
67
|
+
topology,
|
|
68
|
+
[{
|
|
69
|
+
id: "change-1",
|
|
70
|
+
observedAt: "2026-08-23T12:00:00Z",
|
|
71
|
+
material: true,
|
|
72
|
+
crossings: [{ from: "workspace", to: "product", responsibility: "product", interface: "workspace-to-product" }],
|
|
73
|
+
}],
|
|
74
|
+
{ maximumExceptionRate: 0.05 },
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Unknown systems, responsibility mismatches, and an interface name that does
|
|
79
|
+
not match the declared direction count as undeclared crossings. Observations are consumer-supplied
|
|
80
|
+
evidence; the assessment never treats a success flag as proof.
|
|
81
|
+
|
|
82
|
+
A valid topology has at least one system and authority, represents the
|
|
83
|
+
`control-plane` responsibility, binds each authority to a system of record
|
|
84
|
+
that implements its responsibility, and declares interfaces only for
|
|
85
|
+
responsibilities implemented somewhere in the topology.
|
|
86
|
+
|
|
87
|
+
## CLI
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
architect-check topology topology.json
|
|
91
|
+
architect-check exceptions topology.json observations.json --maximum-exception-rate 0.05
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Both commands emit JSON. Exit codes are `0` satisfied, `1` violated, and `2`
|
|
95
|
+
indeterminate or unable to run. Invalid topology is a violation for the
|
|
96
|
+
`topology` conformance command. Invalid inputs make the exception rate
|
|
97
|
+
indeterminate because no valid measurement can be computed.
|
|
98
|
+
|
|
99
|
+
## API
|
|
100
|
+
|
|
101
|
+
| Export | Kind | Purpose |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `defineOperatingTopology(definition)` | function | Returns a detached, fully populated topology without claiming it is valid. |
|
|
104
|
+
| `validateOperatingTopology(value)` | function | Reports structural, identity, responsibility, authority, system-of-record, and interface findings. |
|
|
105
|
+
| `normalizeOperatingTopology(definition)` | function | Applies deterministic property and collection ordering. |
|
|
106
|
+
| `serializeOperatingTopology(definition)` | function | Produces canonical pretty JSON ending in one newline. |
|
|
107
|
+
| `compareOperatingTopologies(previous, next)` | function | Classifies additive and breaking topology contract changes. |
|
|
108
|
+
| `validateArchitectureChangeObservations(value)` | function | Validates independent material-change evidence without certifying it. |
|
|
109
|
+
| `assessArchitectureExceptions(topology, observations, options)` | function | Computes architecture exception rate and satisfied, violated, or indeterminate state. |
|
|
110
|
+
| `OPERATING_SCOPE_KINDS` | constant | Supported `portfolio` and `business` scope kinds. |
|
|
111
|
+
| `OPERATING_SYSTEM_KINDS` | constant | Provider-neutral system kinds. |
|
|
112
|
+
| `OPERATING_RESPONSIBILITIES` | constant | Stable operating-responsibility vocabulary. |
|
|
113
|
+
| `OperatingTopologyDefinition` / `OperatingTopology` | types | Authoring and detached topology shapes. |
|
|
114
|
+
| `OperatingScopeDefinition` / `OperatingScopeKind` | types | Consumer operating boundary and its kind. |
|
|
115
|
+
| `OperatingSystemDefinition` / `OperatingSystemKind` | types | One addressable system and its kind. |
|
|
116
|
+
| `OperatingResponsibility` | type | Stable responsibility assigned to systems and authorities. |
|
|
117
|
+
| `AuthorityDefinition` | type | Consumer-owned responsibility, owner, and system-of-record binding. |
|
|
118
|
+
| `OperatingInterfaceDefinition` | type | One allowed directional system crossing. |
|
|
119
|
+
| `ArchitectureFinding` / `ArchitectureFindingSeverity` | types | Validation issue and severity. |
|
|
120
|
+
| `OperatingTopologyChange` / `OperatingTopologyCompatibilityReport` | types | Classified topology change and compatibility report. |
|
|
121
|
+
| `BoundaryCrossingObservation` / `ArchitectureChangeObservation` | types | One observed crossing and material change. |
|
|
122
|
+
| `ArchitectureAssessmentOptions` / `ArchitectureAssessmentState` | types | Assessment setpoint and three-state result vocabulary. |
|
|
123
|
+
| `AssessedBoundaryCrossing` / `AssessedArchitectureChange` | types | Evidence annotated with declaration and exception status. |
|
|
124
|
+
| `ArchitectureExceptionAssessment` | type | Metric counts, rate, state, evidence details, and findings. |
|
|
125
|
+
|
|
126
|
+
## Ontology
|
|
127
|
+
|
|
128
|
+
`@clossys/architect/ontology` contains the ontology mechanism used by
|
|
129
|
+
the role. Its public model and snapshot API is compatible with the former
|
|
130
|
+
standalone domain package.
|
|
131
|
+
|
|
132
|
+
| Export | Kind | Purpose |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `defineDomainModel(definition)` | function | Returns a detached domain model for authoring. |
|
|
135
|
+
| `validateDomainModel(value)` | function | Validates identifiers, fields, references, relations, and cardinality. |
|
|
136
|
+
| `normalizeDomainModel(model)` | function | Canonically orders a domain model. |
|
|
137
|
+
| `serializeDomainModel(model)` | function | Serializes a canonical domain model. |
|
|
138
|
+
| `compareDomainModels(previous, next)` | function | Classifies additive and breaking model changes. |
|
|
139
|
+
| `defineDomainSnapshot(definition)` | function | Returns a detached collection of records and relations. |
|
|
140
|
+
| `validateDomainSnapshot(model, value)` | function | Validates instance values, types, endpoints, and cardinality. |
|
|
141
|
+
| `normalizeDomainSnapshot(snapshot)` | function | Canonically orders a snapshot and nested values. |
|
|
142
|
+
| `serializeDomainSnapshot(snapshot)` | function | Serializes a canonical snapshot. |
|
|
143
|
+
| `PRIMITIVE_VALUE_TYPES` | constant | Supported primitive value types. |
|
|
144
|
+
| `RELATION_CARDINALITIES` | constant | Supported directed relation cardinalities. |
|
|
145
|
+
| `DomainModelDefinition` / `DomainModel` | types | Model authoring and detached shapes. |
|
|
146
|
+
| `ValueTypeDefinition` / `VocabularyDefinition` | types | Scalar and closed vocabulary declarations. |
|
|
147
|
+
| `DomainTypeDefinition` / `RelationDefinition` | types | Domain types and directed attributed relations. |
|
|
148
|
+
| `FieldDefinition` / `FieldValueType` | types | Stable field and value reference. |
|
|
149
|
+
| `PrimitiveValueType` / `RelationCardinality` | types | Primitive and relation-cardinality vocabularies. |
|
|
150
|
+
| `DomainModelFinding` | type | Domain model validation issue. |
|
|
151
|
+
| `DomainModelChange` / `DomainModelCompatibilityReport` | types | Classified model change and report. |
|
|
152
|
+
| `DomainRecordDefinition` / `DomainRecord` | types | Snapshot record authoring and detached shapes. |
|
|
153
|
+
| `DomainRelationDefinition` / `DomainRelation` | types | Snapshot relation authoring and detached shapes. |
|
|
154
|
+
| `DomainSnapshotDefinition` / `DomainSnapshot` | types | Snapshot authoring and detached shapes. |
|
|
155
|
+
| `DomainSnapshotFinding` | type | Snapshot validation issue. |
|
|
156
|
+
|
|
157
|
+
## Topology compatibility
|
|
158
|
+
|
|
159
|
+
Removing a system, authority, or interface is breaking. Changing a system's
|
|
160
|
+
kind, responsibilities, provider binding, locator, visibility, an authority's
|
|
161
|
+
owner or system of record, or an interface's direction or responsibilities is
|
|
162
|
+
also breaking. Adding a system, authority, or interface is additive. Labels,
|
|
163
|
+
descriptions, and `schemaVersion` do not determine compatibility.
|
|
164
|
+
|
|
165
|
+
## Requirements
|
|
166
|
+
|
|
167
|
+
Node 20+. ESM only. No runtime dependencies and no I/O in the library API.
|
|
168
|
+
|
|
169
|
+
## Licence
|
|
170
|
+
|
|
171
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ArchitectureAssessmentOptions, ArchitectureExceptionAssessment, ArchitectureFinding } from "./types.js";
|
|
2
|
+
/** Validates observations as evidence without using them to certify themselves. */
|
|
3
|
+
export declare function validateArchitectureChangeObservations(value: unknown): ArchitectureFinding[];
|
|
4
|
+
/**
|
|
5
|
+
* Measures the package's primary metric from independent observations.
|
|
6
|
+
* No material observations is explicitly indeterminate, never a zero rate.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assessArchitectureExceptions(topology: unknown, observations: unknown, options: ArchitectureAssessmentOptions): ArchitectureExceptionAssessment;
|
|
9
|
+
//# sourceMappingURL=assessment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assessment.d.ts","sourceRoot":"","sources":["../src/assessment.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,6BAA6B,EAE7B,+BAA+B,EAC/B,mBAAmB,EAKpB,MAAM,YAAY,CAAC;AAYpB,mFAAmF;AACnF,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAuB5F;AAUD;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,OAAO,EACjB,YAAY,EAAE,OAAO,EACrB,OAAO,EAAE,6BAA6B,GACrC,+BAA+B,CAgCjC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { validateOperatingTopology } from "./topology.js";
|
|
2
|
+
import { OPERATING_RESPONSIBILITIES } from "./types.js";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function finding(rule, message, path) {
|
|
7
|
+
return { rule, severity: "error", message, path };
|
|
8
|
+
}
|
|
9
|
+
/** Validates observations as evidence without using them to certify themselves. */
|
|
10
|
+
export function validateArchitectureChangeObservations(value) {
|
|
11
|
+
if (!Array.isArray(value))
|
|
12
|
+
return [finding("observations-shape", "Architecture observations must be an array.", "$")];
|
|
13
|
+
const findings = [];
|
|
14
|
+
const ids = new Set();
|
|
15
|
+
for (const [index, candidate] of value.entries()) {
|
|
16
|
+
const path = `[${index}]`;
|
|
17
|
+
if (!isRecord(candidate)) {
|
|
18
|
+
findings.push(finding("observation-shape", "An observation must be an object.", path));
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (typeof candidate.id !== "string" || candidate.id.length === 0)
|
|
22
|
+
findings.push(finding("observation-id", "id must be a non-empty string.", `${path}.id`));
|
|
23
|
+
else if (ids.has(candidate.id))
|
|
24
|
+
findings.push(finding("duplicate-observation-id", `Duplicate observation id "${candidate.id}".`, `${path}.id`));
|
|
25
|
+
else
|
|
26
|
+
ids.add(candidate.id);
|
|
27
|
+
if (typeof candidate.observedAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(candidate.observedAt))
|
|
28
|
+
findings.push(finding("observed-at", "observedAt must be an RFC 3339 timestamp with an offset.", `${path}.observedAt`));
|
|
29
|
+
if (typeof candidate.material !== "boolean")
|
|
30
|
+
findings.push(finding("material-shape", "material must be a boolean.", `${path}.material`));
|
|
31
|
+
if (!Array.isArray(candidate.crossings)) {
|
|
32
|
+
findings.push(finding("crossings-shape", "crossings must be an array.", `${path}.crossings`));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
for (const [crossingIndex, crossing] of candidate.crossings.entries()) {
|
|
36
|
+
const crossingPath = `${path}.crossings[${crossingIndex}]`;
|
|
37
|
+
if (!isRecord(crossing)) {
|
|
38
|
+
findings.push(finding("crossing-shape", "A boundary crossing must be an object.", crossingPath));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const endpoint of ["from", "to"])
|
|
42
|
+
if (typeof crossing[endpoint] !== "string" || crossing[endpoint].length === 0)
|
|
43
|
+
findings.push(finding("crossing-endpoint", `${endpoint} must be a non-empty system id.`, `${crossingPath}.${endpoint}`));
|
|
44
|
+
if (typeof crossing.responsibility !== "string" || !OPERATING_RESPONSIBILITIES.includes(crossing.responsibility))
|
|
45
|
+
findings.push(finding("crossing-responsibility", "responsibility must be a supported value.", `${crossingPath}.responsibility`));
|
|
46
|
+
if (crossing.from !== undefined && crossing.from === crossing.to)
|
|
47
|
+
findings.push(finding("crossing-boundary", "A boundary crossing must name two different systems.", crossingPath));
|
|
48
|
+
if (crossing.interface !== undefined && (typeof crossing.interface !== "string" || crossing.interface.length === 0))
|
|
49
|
+
findings.push(finding("crossing-interface", "interface must be a non-empty string when provided.", `${crossingPath}.interface`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return findings;
|
|
53
|
+
}
|
|
54
|
+
function isDeclared(crossing, interfaces) {
|
|
55
|
+
return interfaces.some((entry) => entry.from === crossing.from && entry.to === crossing.to && entry.responsibilities.includes(crossing.responsibility) && (crossing.interface === undefined || entry.id === crossing.interface));
|
|
56
|
+
}
|
|
57
|
+
function indeterminate(maximumExceptionRate, observedChanges, findings) {
|
|
58
|
+
return { state: "indeterminate", exceptionRate: null, maximumExceptionRate, observedChanges, observedMaterialChanges: 0, materialChangesWithExceptions: 0, changes: [], findings };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Measures the package's primary metric from independent observations.
|
|
62
|
+
* No material observations is explicitly indeterminate, never a zero rate.
|
|
63
|
+
*/
|
|
64
|
+
export function assessArchitectureExceptions(topology, observations, options) {
|
|
65
|
+
const topologyFindings = validateOperatingTopology(topology);
|
|
66
|
+
const observationFindings = validateArchitectureChangeObservations(observations);
|
|
67
|
+
const optionFindings = [];
|
|
68
|
+
if (!Number.isFinite(options.maximumExceptionRate) || options.maximumExceptionRate < 0 || options.maximumExceptionRate > 1)
|
|
69
|
+
optionFindings.push(finding("maximum-exception-rate", "maximumExceptionRate must be a finite number in [0, 1].", "options.maximumExceptionRate"));
|
|
70
|
+
const observedChanges = Array.isArray(observations) ? observations.length : 0;
|
|
71
|
+
const findings = [...topologyFindings, ...observationFindings, ...optionFindings];
|
|
72
|
+
if (findings.some((entry) => entry.severity === "error"))
|
|
73
|
+
return indeterminate(options.maximumExceptionRate, observedChanges, findings);
|
|
74
|
+
const definition = topology;
|
|
75
|
+
const evidence = observations;
|
|
76
|
+
const interfaces = definition.interfaces ?? [];
|
|
77
|
+
const changes = evidence.map((observation) => {
|
|
78
|
+
const crossings = observation.crossings.map((crossing) => ({ ...crossing, declared: isDeclared(crossing, interfaces) }));
|
|
79
|
+
return { id: observation.id, observedAt: observation.observedAt, material: observation.material, crossings, hasException: observation.material && crossings.some((crossing) => !crossing.declared) };
|
|
80
|
+
});
|
|
81
|
+
const material = changes.filter((change) => change.material);
|
|
82
|
+
if (material.length === 0) {
|
|
83
|
+
return { state: "indeterminate", exceptionRate: null, maximumExceptionRate: options.maximumExceptionRate, observedChanges: changes.length, observedMaterialChanges: 0, materialChangesWithExceptions: 0, changes, findings: [{ rule: "material-evidence-required", severity: "warning", message: "No observed material changes are available; architecture exception rate is unobserved." }] };
|
|
84
|
+
}
|
|
85
|
+
const exceptions = material.filter((change) => change.hasException).length;
|
|
86
|
+
const exceptionRate = exceptions / material.length;
|
|
87
|
+
return {
|
|
88
|
+
state: exceptionRate <= options.maximumExceptionRate ? "satisfied" : "violated",
|
|
89
|
+
exceptionRate,
|
|
90
|
+
maximumExceptionRate: options.maximumExceptionRate,
|
|
91
|
+
observedChanges: changes.length,
|
|
92
|
+
observedMaterialChanges: material.length,
|
|
93
|
+
materialChangesWithExceptions: exceptions,
|
|
94
|
+
changes,
|
|
95
|
+
findings: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=assessment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assessment.js","sourceRoot":"","sources":["../src/assessment.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare class ArchitectCliInputError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/** Testable command dispatcher. Invalid arguments throw and the executable maps them to 2. */
|
|
5
|
+
export declare function main(argv: readonly string[]): number;
|
|
6
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAgBA,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAwCpD,8FAA8F;AAC9F,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAMpD"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { assessArchitectureExceptions } from "./assessment.js";
|
|
6
|
+
import { validateOperatingTopology } from "./topology.js";
|
|
7
|
+
const USAGE = `Usage: architect-check topology <topology-file>
|
|
8
|
+
architect-check exceptions <topology-file> <observations-file> --maximum-exception-rate <rate>
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
topology Validate a provider-neutral operating topology.
|
|
12
|
+
exceptions Assess architecture exception rate from observed changes.
|
|
13
|
+
|
|
14
|
+
Exit codes: 0 = satisfied, 1 = violated, 2 = indeterminate or could not run.`;
|
|
15
|
+
export class ArchitectCliInputError extends Error {
|
|
16
|
+
}
|
|
17
|
+
function readJson(path, label) {
|
|
18
|
+
const resolved = resolve(path);
|
|
19
|
+
if (!existsSync(resolved))
|
|
20
|
+
throw new ArchitectCliInputError(`${label} "${path}" does not exist`);
|
|
21
|
+
try {
|
|
22
|
+
if (!statSync(resolved).isFile())
|
|
23
|
+
throw new ArchitectCliInputError(`${label} "${path}" is not a file`);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof ArchitectCliInputError)
|
|
27
|
+
throw error;
|
|
28
|
+
throw new ArchitectCliInputError(`cannot inspect ${label} "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(resolved, "utf8"));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new ArchitectCliInputError(`${label} "${path}" is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function topologyCommand(argv) {
|
|
38
|
+
if (argv.length !== 1)
|
|
39
|
+
throw new ArchitectCliInputError("topology requires exactly one topology-file");
|
|
40
|
+
const findings = validateOperatingTopology(readJson(argv[0], "topology-file"));
|
|
41
|
+
const report = { state: findings.some((entry) => entry.severity === "error") ? "violated" : "satisfied", findings };
|
|
42
|
+
console.log(JSON.stringify(report, null, 2));
|
|
43
|
+
return report.state === "satisfied" ? 0 : 1;
|
|
44
|
+
}
|
|
45
|
+
function exceptionsCommand(argv) {
|
|
46
|
+
let topologyFile;
|
|
47
|
+
let observationsFile;
|
|
48
|
+
let rate;
|
|
49
|
+
for (let index = 0; index < argv.length; index++) {
|
|
50
|
+
const argument = argv[index];
|
|
51
|
+
if (argument === "--maximum-exception-rate") {
|
|
52
|
+
const value = argv[++index];
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
throw new ArchitectCliInputError("--maximum-exception-rate requires a value");
|
|
55
|
+
rate = Number(value);
|
|
56
|
+
}
|
|
57
|
+
else if (argument.startsWith("-"))
|
|
58
|
+
throw new ArchitectCliInputError(`unknown option "${argument}"`);
|
|
59
|
+
else if (topologyFile === undefined)
|
|
60
|
+
topologyFile = argument;
|
|
61
|
+
else if (observationsFile === undefined)
|
|
62
|
+
observationsFile = argument;
|
|
63
|
+
else
|
|
64
|
+
throw new ArchitectCliInputError(`unexpected extra argument "${argument}"`);
|
|
65
|
+
}
|
|
66
|
+
if (topologyFile === undefined || observationsFile === undefined || rate === undefined)
|
|
67
|
+
throw new ArchitectCliInputError("exceptions requires topology-file, observations-file, and --maximum-exception-rate");
|
|
68
|
+
const report = assessArchitectureExceptions(readJson(topologyFile, "topology-file"), readJson(observationsFile, "observations-file"), { maximumExceptionRate: rate });
|
|
69
|
+
console.log(JSON.stringify(report, null, 2));
|
|
70
|
+
return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
|
|
71
|
+
}
|
|
72
|
+
/** Testable command dispatcher. Invalid arguments throw and the executable maps them to 2. */
|
|
73
|
+
export function main(argv) {
|
|
74
|
+
if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
|
|
75
|
+
console.log(USAGE);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
const [command, ...rest] = argv;
|
|
79
|
+
if (command === "topology")
|
|
80
|
+
return topologyCommand(rest);
|
|
81
|
+
if (command === "exceptions")
|
|
82
|
+
return exceptionsCommand(rest);
|
|
83
|
+
throw new ArchitectCliInputError(command === undefined ? "a command is required" : `unknown command "${command}"`);
|
|
84
|
+
}
|
|
85
|
+
function run() {
|
|
86
|
+
try {
|
|
87
|
+
process.exitCode = main(process.argv.slice(2));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error(`architect-check: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
+
process.exitCode = 2;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === resolve(process.argv[1]))
|
|
95
|
+
run();
|
|
96
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Provider-neutral operating-architecture contracts and evidence-based assessment. */
|
|
2
|
+
export { OPERATING_RESPONSIBILITIES, OPERATING_SCOPE_KINDS, OPERATING_SYSTEM_KINDS, } from "./types.js";
|
|
3
|
+
export { compareOperatingTopologies, defineOperatingTopology, normalizeOperatingTopology, serializeOperatingTopology, validateOperatingTopology, } from "./topology.js";
|
|
4
|
+
export { assessArchitectureExceptions, validateArchitectureChangeObservations, } from "./assessment.js";
|
|
5
|
+
export type { ArchitectureAssessmentOptions, ArchitectureAssessmentState, ArchitectureChangeObservation, ArchitectureExceptionAssessment, ArchitectureFinding, ArchitectureFindingSeverity, AssessedArchitectureChange, AssessedBoundaryCrossing, AuthorityDefinition, BoundaryCrossingObservation, OperatingInterfaceDefinition, OperatingResponsibility, OperatingScopeDefinition, OperatingScopeKind, OperatingSystemDefinition, OperatingSystemKind, OperatingTopology, OperatingTopologyChange, OperatingTopologyCompatibilityReport, OperatingTopologyDefinition, } from "./types.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,4BAA4B,EAC5B,sCAAsC,GACvC,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,6BAA6B,EAC7B,2BAA2B,EAC3B,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,4BAA4B,EAC5B,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,oCAAoC,EACpC,2BAA2B,GAC5B,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Provider-neutral operating-architecture contracts and evidence-based assessment. */
|
|
2
|
+
export { OPERATING_RESPONSIBILITIES, OPERATING_SCOPE_KINDS, OPERATING_SYSTEM_KINDS, } from "./types.js";
|
|
3
|
+
export { compareOperatingTopologies, defineOperatingTopology, normalizeOperatingTopology, serializeOperatingTopology, validateOperatingTopology, } from "./topology.js";
|
|
4
|
+
export { assessArchitectureExceptions, validateArchitectureChangeObservations, } from "./assessment.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,4BAA4B,EAC5B,sCAAsC,GACvC,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DomainModelCompatibilityReport, DomainModelDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compares actual model content. Labels and `schemaVersion` are intentionally
|
|
4
|
+
* not compatibility surface; stable identifiers and structural contracts are.
|
|
5
|
+
*/
|
|
6
|
+
export declare function compareDomainModels(previous: DomainModelDefinition, next: DomainModelDefinition): DomainModelCompatibilityReport;
|
|
7
|
+
//# sourceMappingURL=compatibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../../src/ontology/compatibility.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAkC,8BAA8B,EAAE,qBAAqB,EAAwG,MAAM,YAAY,CAAC;AAsG9N;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,qBAAqB,EAAE,IAAI,EAAE,qBAAqB,GAAG,8BAA8B,CAuBhI"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { normalizeDomainModel } from "./normalize.js";
|
|
2
|
+
import { validateDomainModel } from "./validate.js";
|
|
3
|
+
function mapById(items) {
|
|
4
|
+
return new Map(items.map((item) => [item.id, item]));
|
|
5
|
+
}
|
|
6
|
+
function stable(value) {
|
|
7
|
+
return JSON.stringify(value);
|
|
8
|
+
}
|
|
9
|
+
function compareFields(previous, next, subject, ownerId, changes) {
|
|
10
|
+
const before = mapById(previous);
|
|
11
|
+
const after = mapById(next);
|
|
12
|
+
for (const [id, field] of before) {
|
|
13
|
+
const replacement = after.get(id);
|
|
14
|
+
if (replacement === undefined) {
|
|
15
|
+
changes.push({ kind: "breaking", subject, id, message: `${subject} "${id}" was removed from "${ownerId}".` });
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (field.valueType !== replacement.valueType) {
|
|
19
|
+
changes.push({ kind: "breaking", subject, id, message: `${subject} "${id}" changed valueType.` });
|
|
20
|
+
}
|
|
21
|
+
if (field.required !== true && replacement.required === true) {
|
|
22
|
+
changes.push({ kind: "breaking", subject, id, message: `${subject} "${id}" became required.` });
|
|
23
|
+
}
|
|
24
|
+
if (field.required === true && replacement.required !== true) {
|
|
25
|
+
changes.push({ kind: "additive", subject, id, message: `${subject} "${id}" became optional.` });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [id, field] of after) {
|
|
29
|
+
if (!before.has(id)) {
|
|
30
|
+
changes.push({
|
|
31
|
+
kind: field.required === true ? "breaking" : "additive",
|
|
32
|
+
subject,
|
|
33
|
+
id,
|
|
34
|
+
message: `${subject} "${id}" was added to "${ownerId}"${field.required === true ? " as required" : ""}.`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function compareValueTypes(previous, next, changes) {
|
|
40
|
+
const before = mapById(previous);
|
|
41
|
+
const after = mapById(next);
|
|
42
|
+
for (const [id, valueType] of before) {
|
|
43
|
+
const replacement = after.get(id);
|
|
44
|
+
if (replacement === undefined)
|
|
45
|
+
changes.push({ kind: "breaking", subject: "value-type", id, message: `Value type "${id}" was removed.` });
|
|
46
|
+
else if (valueType.primitive !== replacement.primitive)
|
|
47
|
+
changes.push({ kind: "breaking", subject: "value-type", id, message: `Value type "${id}" changed primitive.` });
|
|
48
|
+
}
|
|
49
|
+
for (const [id] of after)
|
|
50
|
+
if (!before.has(id))
|
|
51
|
+
changes.push({ kind: "additive", subject: "value-type", id, message: `Value type "${id}" was added.` });
|
|
52
|
+
}
|
|
53
|
+
function compareVocabularies(previous, next, changes) {
|
|
54
|
+
const before = mapById(previous);
|
|
55
|
+
const after = mapById(next);
|
|
56
|
+
for (const [id, vocabulary] of before) {
|
|
57
|
+
const replacement = after.get(id);
|
|
58
|
+
if (replacement === undefined) {
|
|
59
|
+
changes.push({ kind: "breaking", subject: "vocabulary", id, message: `Vocabulary "${id}" was removed.` });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const previousValues = new Set(vocabulary.values);
|
|
63
|
+
const nextValues = new Set(replacement.values);
|
|
64
|
+
for (const value of previousValues)
|
|
65
|
+
if (!nextValues.has(value))
|
|
66
|
+
changes.push({ kind: "breaking", subject: "vocabulary-value", id: `${id}.${value}`, message: `Vocabulary value "${value}" was removed from "${id}".` });
|
|
67
|
+
for (const value of nextValues)
|
|
68
|
+
if (!previousValues.has(value))
|
|
69
|
+
changes.push({ kind: "additive", subject: "vocabulary-value", id: `${id}.${value}`, message: `Vocabulary value "${value}" was added to "${id}".` });
|
|
70
|
+
}
|
|
71
|
+
for (const [id] of after)
|
|
72
|
+
if (!before.has(id))
|
|
73
|
+
changes.push({ kind: "additive", subject: "vocabulary", id, message: `Vocabulary "${id}" was added.` });
|
|
74
|
+
}
|
|
75
|
+
function compareTypes(previous, next, changes) {
|
|
76
|
+
const before = mapById(previous);
|
|
77
|
+
const after = mapById(next);
|
|
78
|
+
for (const [id, type] of before) {
|
|
79
|
+
const replacement = after.get(id);
|
|
80
|
+
if (replacement === undefined)
|
|
81
|
+
changes.push({ kind: "breaking", subject: "type", id, message: `Type "${id}" was removed.` });
|
|
82
|
+
else
|
|
83
|
+
compareFields(type.fields ?? [], replacement.fields ?? [], "field", id, changes);
|
|
84
|
+
}
|
|
85
|
+
for (const [id] of after)
|
|
86
|
+
if (!before.has(id))
|
|
87
|
+
changes.push({ kind: "additive", subject: "type", id, message: `Type "${id}" was added.` });
|
|
88
|
+
}
|
|
89
|
+
function compareRelations(previous, next, changes) {
|
|
90
|
+
const before = mapById(previous);
|
|
91
|
+
const after = mapById(next);
|
|
92
|
+
for (const [id, relation] of before) {
|
|
93
|
+
const replacement = after.get(id);
|
|
94
|
+
if (replacement === undefined) {
|
|
95
|
+
changes.push({ kind: "breaking", subject: "relation", id, message: `Relation "${id}" was removed.` });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (relation.from !== replacement.from || relation.to !== replacement.to)
|
|
99
|
+
changes.push({ kind: "breaking", subject: "relation", id, message: `Relation "${id}" changed an endpoint.` });
|
|
100
|
+
if (relation.cardinality !== replacement.cardinality)
|
|
101
|
+
changes.push({ kind: "breaking", subject: "relation", id, message: `Relation "${id}" changed cardinality.` });
|
|
102
|
+
compareFields(relation.properties ?? [], replacement.properties ?? [], "relation-property", id, changes);
|
|
103
|
+
}
|
|
104
|
+
for (const [id] of after)
|
|
105
|
+
if (!before.has(id))
|
|
106
|
+
changes.push({ kind: "additive", subject: "relation", id, message: `Relation "${id}" was added.` });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compares actual model content. Labels and `schemaVersion` are intentionally
|
|
110
|
+
* not compatibility surface; stable identifiers and structural contracts are.
|
|
111
|
+
*/
|
|
112
|
+
export function compareDomainModels(previous, next) {
|
|
113
|
+
const previousFindings = validateDomainModel(previous);
|
|
114
|
+
const nextFindings = validateDomainModel(next);
|
|
115
|
+
if (previousFindings.some((entry) => entry.severity === "error") || nextFindings.some((entry) => entry.severity === "error")) {
|
|
116
|
+
return { compatible: false, changes: [], previousFindings, nextFindings };
|
|
117
|
+
}
|
|
118
|
+
const before = normalizeDomainModel(previous);
|
|
119
|
+
const after = normalizeDomainModel(next);
|
|
120
|
+
const changes = [];
|
|
121
|
+
if (before.id !== after.id)
|
|
122
|
+
changes.push({ kind: "breaking", subject: "model", id: after.id, message: `Model id changed from "${before.id}" to "${after.id}".` });
|
|
123
|
+
compareValueTypes(before.valueTypes, after.valueTypes, changes);
|
|
124
|
+
compareVocabularies(before.vocabularies, after.vocabularies, changes);
|
|
125
|
+
compareTypes(before.types, after.types, changes);
|
|
126
|
+
compareRelations(before.relations, after.relations, changes);
|
|
127
|
+
changes.sort((left, right) => stable(left).localeCompare(stable(right)));
|
|
128
|
+
return {
|
|
129
|
+
compatible: changes.every((entry) => entry.kind !== "breaking"),
|
|
130
|
+
changes,
|
|
131
|
+
previousFindings,
|
|
132
|
+
nextFindings,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=compatibility.js.map
|