@intentset/architecture 0.1.0 → 0.3.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/areas.d.ts +33 -0
- package/dist/areas.d.ts.map +1 -0
- package/dist/areas.js +189 -0
- package/dist/areas.js.map +1 -0
- package/dist/boundaries.d.ts +0 -40
- package/dist/boundaries.d.ts.map +1 -1
- package/dist/boundaries.js +18 -2
- package/dist/boundaries.js.map +1 -1
- package/dist/check.d.ts +18 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +32 -3
- package/dist/check.js.map +1 -1
- package/dist/claims.d.ts +2 -1
- package/dist/claims.d.ts.map +1 -1
- package/dist/claims.js +7 -2
- package/dist/claims.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +78 -1
- package/dist/config.js.map +1 -1
- package/dist/extract.d.ts +33 -0
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +129 -0
- package/dist/extract.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/ownership.d.ts +3 -1
- package/dist/ownership.d.ts.map +1 -1
- package/dist/ownership.js +5 -1
- package/dist/ownership.js.map +1 -1
- package/dist/regions.d.ts.map +1 -1
- package/dist/regions.js +3 -1
- package/dist/regions.js.map +1 -1
- package/package.json +2 -2
- package/src/areas.ts +233 -0
- package/src/boundaries.ts +21 -2
- package/src/check.ts +51 -3
- package/src/claims.ts +7 -2
- package/src/config.ts +92 -1
- package/src/extract.ts +150 -0
- package/src/index.ts +16 -1
- package/src/ownership.ts +8 -2
- package/src/regions.ts +3 -1
package/dist/regions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regions.d.ts","sourceRoot":"","sources":["../src/regions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD,MAAM,MAAM,UAAU,GAClB,OAAO,GACP,aAAa,GACb,QAAQ,GACR,gBAAgB,GAChB,UAAU,GACV,SAAS,GACT,SAAS,GACT,UAAU,CAAC;AAEf,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,qEAAqE;IACrE,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;IACvB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,uCAAuC;IACvC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,+CAA+C;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,sCAAsC;IACtC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,yFAAyF;IACzF,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IAC7D,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,mEAAmE;IACnE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,EAAE,CAOrD;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,MAAM,EAAE,kBAAkB,GACzB,KAAK,
|
|
1
|
+
{"version":3,"file":"regions.d.ts","sourceRoot":"","sources":["../src/regions.ts"],"names":[],"mappings":"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"}
|
package/dist/regions.js
CHANGED
|
@@ -67,7 +67,9 @@ export function buildModel(graph, registries, tree, config) {
|
|
|
67
67
|
index,
|
|
68
68
|
files: validatePattern(resource.path) === null ? production.filter((file) => matchPattern(resource.path, file)) : [],
|
|
69
69
|
}));
|
|
70
|
-
|
|
70
|
+
// An area's backend is backend for AMP001, AMP002 and AMP004 as the unified one is (profile §9).
|
|
71
|
+
const backendPatterns = [...config.backendRoots, ...config.areas.flatMap((area) => area.backend)];
|
|
72
|
+
const underBackend = (path) => matchAny(backendPatterns, path);
|
|
71
73
|
const regions = new Map();
|
|
72
74
|
const regionOf = (path) => {
|
|
73
75
|
const cached = regions.get(path);
|
package/dist/regions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regions.js","sourceRoot":"","sources":["../src/regions.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"regions.js","sourceRoot":"","sources":["../src/regions.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentset/architecture",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Traceable VSA checks: path claims, import graph, layer matrix, resources, exceptions, baseline.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@intentset/core": "^0.
|
|
31
|
+
"@intentset/core": "^0.3.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"typescript": "^7.0.2"
|
package/src/areas.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Areas (profile §9): a backend split into several Amplify backends, each its
|
|
3
|
+
* own CloudFormation deployment and source API, behind one AppSync Merged API.
|
|
4
|
+
* Nothing here runs unless `.intentset/architecture.yaml` declares areas.
|
|
5
|
+
*
|
|
6
|
+
* AMP007 a slice names a declared area as its domain, and its claims stay in that area
|
|
7
|
+
* AMP008 no area backend imports another; the shared backend package imports no area
|
|
8
|
+
* AMP009 each model, enum and operation is declared in one area's schema (a custom type: a warning)
|
|
9
|
+
* AMP010 no relationship or reference names another area's type
|
|
10
|
+
* AMP011 only the schema bridge calls generateClient (its schema imports are checked with AMP001)
|
|
11
|
+
*
|
|
12
|
+
* AMP012 (cross-area access and an acyclic deploy order) and AMP013 (one
|
|
13
|
+
* authorizer on the Merged API) are review assertions: neither is visible in
|
|
14
|
+
* source, so they are listed for review and never reported as passing.
|
|
15
|
+
*/
|
|
16
|
+
import type { AreaConfig } from "./config.ts";
|
|
17
|
+
import { extractSchema, findCalls, type SchemaKind } from "./extract.ts";
|
|
18
|
+
import { type Finding, finding, listSome } from "./finding.ts";
|
|
19
|
+
import type { ImportEdge } from "./imports.ts";
|
|
20
|
+
import { isCode } from "./paths.ts";
|
|
21
|
+
import { compareStrings, matchAny } from "./patterns.ts";
|
|
22
|
+
import type { Model } from "./regions.ts";
|
|
23
|
+
|
|
24
|
+
export const AREA_REVIEW_REQUIRED = [
|
|
25
|
+
"AMP012 backend code reaches another area's data through published resource names and the AWS SDK, and deploy-time dependencies between areas are acyclic: deployment wiring, not source",
|
|
26
|
+
"AMP013 one authorizer on the Merged API decides every request to every area and denies what it does not list: an authorizer's policy, not source",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export interface AreaCheck {
|
|
30
|
+
findings: Finding[];
|
|
31
|
+
/** What could not be read: never a pass (invariant 6). */
|
|
32
|
+
unresolved: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The area whose backend holds a path, or null. */
|
|
36
|
+
export function backendAreaOf(areas: readonly AreaConfig[], path: string): AreaConfig | null {
|
|
37
|
+
return areas.find((area) => matchAny(area.backend, path)) ?? null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The area whose backend or frontend holds a path, or null. */
|
|
41
|
+
export function areaOf(areas: readonly AreaConfig[], path: string): AreaConfig | null {
|
|
42
|
+
return backendAreaOf(areas, path) ?? areas.find((area) => matchAny(area.frontend, path)) ?? null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** True when the path is one of the declared areas' schema files. */
|
|
46
|
+
export function isAreaSchema(areas: readonly AreaConfig[], path: string): boolean {
|
|
47
|
+
return areas.some((area) => area.schema === path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const OPERATION_KINDS = new Set<SchemaKind | null>(["model", "enum", "query", "mutation", "subscription", null]);
|
|
51
|
+
|
|
52
|
+
export function checkAreas(model: Model, edges: readonly ImportEdge[]): AreaCheck {
|
|
53
|
+
const { areas, sharedBackend, schemaBridge } = model.config;
|
|
54
|
+
const findings: Finding[] = [];
|
|
55
|
+
const unresolved: string[] = [];
|
|
56
|
+
if (areas.length === 0) return { findings, unresolved };
|
|
57
|
+
const names = areas.map((area) => area.name).join(", ");
|
|
58
|
+
|
|
59
|
+
// AMP007: every slice in a declared area, and its claims inside it.
|
|
60
|
+
for (const slice of model.slices) {
|
|
61
|
+
const area = areas.find((candidate) => candidate.name === slice.meta.domain);
|
|
62
|
+
if (area === undefined) {
|
|
63
|
+
findings.push(
|
|
64
|
+
finding({
|
|
65
|
+
code: "AMP007",
|
|
66
|
+
artifact: slice.id,
|
|
67
|
+
path: slice.path,
|
|
68
|
+
field: "/intentset/slice/domain",
|
|
69
|
+
message: `${slice.id} names the domain "${slice.meta.domain}", which is not a declared area; the areas are ${names}.`,
|
|
70
|
+
remediation:
|
|
71
|
+
"Set domain to the area whose backend serves the slice, or declare the area in .intentset/architecture.yaml (profile §9, AMP007).",
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
slice.meta.claims.forEach((claim, i) => {
|
|
77
|
+
const foreign = slice.claimFiles[i].filter((file) => {
|
|
78
|
+
const other = areaOf(areas, file);
|
|
79
|
+
return other !== null && other.name !== area.name;
|
|
80
|
+
});
|
|
81
|
+
if (foreign.length === 0) return;
|
|
82
|
+
const others = [...new Set(foreign.map((file) => areaOf(areas, file)?.name as string))].sort(compareStrings);
|
|
83
|
+
findings.push(
|
|
84
|
+
finding({
|
|
85
|
+
code: "AMP007",
|
|
86
|
+
artifact: slice.id,
|
|
87
|
+
path: slice.path,
|
|
88
|
+
field: `/intentset/slice/claims/${i}/path`,
|
|
89
|
+
message: `The ${claim.kind} claim ${claim.path} of ${slice.id}, a slice of area ${area.name}, reaches into area ${others.join(" and ")}: ${listSome(foreign)}.`,
|
|
90
|
+
remediation:
|
|
91
|
+
"Narrow the claim to the slice's own area, or move the slice to the area that holds those files (profile §9, AMP007).",
|
|
92
|
+
paths: foreign,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// AMP008: area backends deploy apart, so neither imports the other; shared backend code imports no area.
|
|
99
|
+
for (const edge of edges) {
|
|
100
|
+
if (edge.to === null || !model.inScope(edge.from)) continue;
|
|
101
|
+
const to = backendAreaOf(areas, edge.to);
|
|
102
|
+
if (to === null) continue;
|
|
103
|
+
const from = backendAreaOf(areas, edge.from);
|
|
104
|
+
const location = { line: edge.line };
|
|
105
|
+
if (from !== null && from.name !== to.name) {
|
|
106
|
+
findings.push(
|
|
107
|
+
finding({
|
|
108
|
+
code: "AMP008",
|
|
109
|
+
artifact: null,
|
|
110
|
+
path: edge.from,
|
|
111
|
+
location,
|
|
112
|
+
message: `${edge.from}, in the ${from.name} backend, imports ${edge.to} from the ${to.name} backend, and area backends deploy separately.`,
|
|
113
|
+
remediation:
|
|
114
|
+
"Move what both need into the shared backend package, and reach the other area's data through its published resource names (profile §9, AMP008, AMP012).",
|
|
115
|
+
edges: [{ from: edge.from, to: edge.to }],
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
} else if (from === null && matchAny(sharedBackend, edge.from)) {
|
|
119
|
+
findings.push(
|
|
120
|
+
finding({
|
|
121
|
+
code: "AMP008",
|
|
122
|
+
artifact: null,
|
|
123
|
+
path: edge.from,
|
|
124
|
+
location,
|
|
125
|
+
message: `${edge.from}, in the shared backend package, imports ${edge.to} from the ${to.name} backend, and the shared package must stay domain-neutral.`,
|
|
126
|
+
remediation:
|
|
127
|
+
"Pass what the shared code needs in as a parameter, or move the code into the area that owns it (profile §9, AMP008).",
|
|
128
|
+
edges: [{ from: edge.from, to: edge.to }],
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// AMP009 and AMP010: one owner per name across the areas' schemas, and no reference across them.
|
|
135
|
+
const declared = new Map<string, { area: string; kind: SchemaKind | null; schema: string; line: number }[]>();
|
|
136
|
+
const read = new Map<string, ReturnType<typeof extractSchema>>();
|
|
137
|
+
for (const area of areas) {
|
|
138
|
+
const text = model.files.get(area.schema);
|
|
139
|
+
if (text === undefined) {
|
|
140
|
+
findings.push(
|
|
141
|
+
finding({
|
|
142
|
+
code: "AMP009",
|
|
143
|
+
artifact: null,
|
|
144
|
+
path: area.schema,
|
|
145
|
+
message: `The schema of area ${area.name}, ${area.schema}, is not a file in the repository, so its declarations were not checked.`,
|
|
146
|
+
remediation: "Correct the area's schema path in .intentset/architecture.yaml (profile §9).",
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const extraction = extractSchema(area.schema, text);
|
|
152
|
+
read.set(area.name, extraction);
|
|
153
|
+
if (!extraction.found) {
|
|
154
|
+
unresolved.push(
|
|
155
|
+
`${area.schema}: no <builder>.schema({ ... }) call, so area ${area.name}'s declarations were not read`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
for (const problem of extraction.problems) unresolved.push(`${area.schema}:${problem.line}: ${problem.message}`);
|
|
159
|
+
for (const declaration of extraction.declarations) {
|
|
160
|
+
const list = declared.get(declaration.name) ?? [];
|
|
161
|
+
list.push({ area: area.name, kind: declaration.kind, schema: area.schema, line: declaration.line });
|
|
162
|
+
declared.set(declaration.name, list);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const name of [...declared.keys()].sort(compareStrings)) {
|
|
166
|
+
const owners = declared.get(name) ?? [];
|
|
167
|
+
const inAreas = [...new Set(owners.map((owner) => owner.area))].sort(compareStrings);
|
|
168
|
+
if (inAreas.length < 2) continue;
|
|
169
|
+
const later = owners.find((owner) => owner.area === inAreas[1]) ?? owners[1];
|
|
170
|
+
const operation = owners.some((owner) => OPERATION_KINDS.has(owner.kind));
|
|
171
|
+
findings.push(
|
|
172
|
+
finding({
|
|
173
|
+
code: "AMP009",
|
|
174
|
+
...(operation ? {} : { severity: "warning" as const }),
|
|
175
|
+
artifact: null,
|
|
176
|
+
path: later.schema,
|
|
177
|
+
location: { line: later.line },
|
|
178
|
+
message: operation
|
|
179
|
+
? `${name} is declared in the schemas of areas ${inAreas.join(" and ")}; the Merged API joins every area into one namespace and refuses a field two sources resolve.`
|
|
180
|
+
: `The custom type ${name} is declared in the schemas of areas ${inAreas.join(" and ")}, which share one type namespace in the Merged API.`,
|
|
181
|
+
remediation: operation
|
|
182
|
+
? `Keep ${name} in the one area whose functions read and write it, and link to it from the others by ID (profile §9, AMP009).`
|
|
183
|
+
: `Prefix the type with its area's name, or name it after the operation that returns it (profile §9, AMP009).`,
|
|
184
|
+
paths: [...new Set(owners.map((owner) => owner.schema))].sort(compareStrings),
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
for (const area of areas) {
|
|
189
|
+
const extraction = read.get(area.name);
|
|
190
|
+
if (extraction === undefined) continue;
|
|
191
|
+
const own = new Set(extraction.declarations.map((declaration) => declaration.name));
|
|
192
|
+
for (const reference of extraction.references) {
|
|
193
|
+
if (own.has(reference.target)) continue;
|
|
194
|
+
const elsewhere = (declared.get(reference.target) ?? []).map((owner) => owner.area);
|
|
195
|
+
if (elsewhere.length === 0) continue;
|
|
196
|
+
findings.push(
|
|
197
|
+
finding({
|
|
198
|
+
code: "AMP010",
|
|
199
|
+
artifact: null,
|
|
200
|
+
path: area.schema,
|
|
201
|
+
location: { line: reference.line },
|
|
202
|
+
message: `${area.schema} ${reference.via}("${reference.target}") names a type that area ${elsewhere.sort(compareStrings)[0]} declares; relationships and references do not cross areas.`,
|
|
203
|
+
remediation:
|
|
204
|
+
"Store the other area's ID as a plain field and resolve it in a function of this area (profile §9, AMP010).",
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// AMP011: one client, on the Merged API, made in one place.
|
|
211
|
+
for (const [path, text] of model.files) {
|
|
212
|
+
if (path === schemaBridge || !isCode(path) || model.isTest(path) || !model.inScope(path)) continue;
|
|
213
|
+
if (model.underBackend(path) || matchAny(sharedBackend, path)) continue;
|
|
214
|
+
if (!text.includes("generateClient")) continue;
|
|
215
|
+
const lines = findCalls(path, text, "generateClient");
|
|
216
|
+
if (lines.length === 0) continue;
|
|
217
|
+
findings.push(
|
|
218
|
+
finding({
|
|
219
|
+
code: "AMP011",
|
|
220
|
+
artifact: null,
|
|
221
|
+
path,
|
|
222
|
+
location: { line: lines[0] },
|
|
223
|
+
message:
|
|
224
|
+
schemaBridge === null
|
|
225
|
+
? `${path} calls generateClient, and this repository declares no schema bridge to make the one client on the Merged API.`
|
|
226
|
+
: `${path} calls generateClient; only the schema bridge ${schemaBridge} makes the client, once, on the Merged API.`,
|
|
227
|
+
remediation:
|
|
228
|
+
"Declare schemaBridge in .intentset/architecture.yaml and import the client from it everywhere else (profile §9, AMP011).",
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return { findings, unresolved };
|
|
233
|
+
}
|
package/src/boundaries.ts
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* so a narrow scope cannot hide its consumers and they do not fail it
|
|
39
39
|
* (VSA §9). They count toward no edge total and no slice dependency.
|
|
40
40
|
*/
|
|
41
|
+
import { isAreaSchema } from "./areas.ts";
|
|
41
42
|
import { type Finding, finding, listSome } from "./finding.ts";
|
|
42
43
|
import type { ImportEdge } from "./imports.ts";
|
|
43
44
|
import { inSeam, isPure, knownLayer, layerAllows, layerOf } from "./layers.ts";
|
|
@@ -281,9 +282,27 @@ export function checkBoundaries(model: Model, edges: readonly ImportEdge[], reso
|
|
|
281
282
|
if (!model.inScope(to) && target === undefined) continue;
|
|
282
283
|
const subject = { edges: [{ from, to }, ...(source && target ? [{ from: source.id, to: target.id }] : [])] };
|
|
283
284
|
|
|
284
|
-
// 1. AMP001
|
|
285
|
-
|
|
285
|
+
// 1. AMP001, and AMP011 for an area repository's schema bridge (profile §9). The shared backend
|
|
286
|
+
// package is backend code: what it may import is AMP008's question.
|
|
287
|
+
if (model.underBackend(to) && !model.underBackend(from) && !matchAny(config.sharedBackend, from)) {
|
|
286
288
|
const seam = source !== undefined && inSeam(source, from);
|
|
289
|
+
if (edge.typeOnly && isAreaSchema(config.areas, to)) {
|
|
290
|
+
if (from !== config.schemaBridge) {
|
|
291
|
+
findings.push(
|
|
292
|
+
finding({
|
|
293
|
+
code: "AMP011",
|
|
294
|
+
artifact,
|
|
295
|
+
path: from,
|
|
296
|
+
location,
|
|
297
|
+
message: `${from} ${how(edge)}, the schema of an area, and only the schema bridge${config.schemaBridge === null ? ", which this repository has not declared," : ` ${config.schemaBridge}`} may.`,
|
|
298
|
+
remediation:
|
|
299
|
+
"Import the client and its types from the schema bridge, which joins every area's schema into one type (profile §9, AMP011).",
|
|
300
|
+
...subject,
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
287
306
|
if (!(seam && matchAny(config.responseParserModules, to))) {
|
|
288
307
|
findings.push(
|
|
289
308
|
finding({
|
package/src/check.ts
CHANGED
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
* migration mode. What could not be checked is listed in `unresolved` and,
|
|
12
12
|
* for imports, reported as a TS004 warning: never a pass (invariant 6).
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type Diagnostic,
|
|
16
|
+
type Graph,
|
|
17
|
+
type Level,
|
|
18
|
+
type OwnershipEntry,
|
|
19
|
+
type OwnershipReportSection,
|
|
20
|
+
type Registries,
|
|
21
|
+
sortDiagnostics,
|
|
22
|
+
} from "@intentset/core";
|
|
23
|
+
import { AREA_REVIEW_REQUIRED, checkAreas } from "./areas.ts";
|
|
15
24
|
import { applyBaseline, type BaselineEntry, type Mode } from "./baseline.ts";
|
|
16
25
|
import { checkBoundaries, checkUnclassified } from "./boundaries.ts";
|
|
17
26
|
import { checkClaims } from "./claims.ts";
|
|
@@ -76,6 +85,14 @@ export interface ArchitectureResult {
|
|
|
76
85
|
summary: ArchitectureSummary;
|
|
77
86
|
/** The resolved import graph, for reports and impact views. */
|
|
78
87
|
edges: ImportEdge[];
|
|
88
|
+
/**
|
|
89
|
+
* Where every file falls, sorted by path: each production file's region and
|
|
90
|
+
* owner, every test file with the slice its claims match, and nothing for a
|
|
91
|
+
* file in the external region (root configuration, scripts, documents that
|
|
92
|
+
* nothing claims). Out-of-scope files are attributed too: scope decides what
|
|
93
|
+
* is reported, not who owns what.
|
|
94
|
+
*/
|
|
95
|
+
ownership: OwnershipEntry[];
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
/** VSA §2: the rules that are review assertions, snapshot diffs or contract diffs rather than structure. */
|
|
@@ -149,6 +166,9 @@ export function checkArchitecture(
|
|
|
149
166
|
const boundaries = checkBoundaries(model, imports.edges, resolver);
|
|
150
167
|
findings.push(...boundaries.findings);
|
|
151
168
|
findings.push(...checkUnclassified(model));
|
|
169
|
+
const areaCheck = checkAreas(model, imports.edges);
|
|
170
|
+
findings.push(...areaCheck.findings);
|
|
171
|
+
unresolved.push(...areaCheck.unresolved);
|
|
152
172
|
const dependencies = checkDependencies(model, graph, boundaries.sliceEdges);
|
|
153
173
|
findings.push(...dependencies.findings);
|
|
154
174
|
|
|
@@ -191,7 +211,35 @@ export function checkArchitecture(
|
|
|
191
211
|
baselined: baseline.baselined,
|
|
192
212
|
baselineStale: baseline.stale.length,
|
|
193
213
|
unresolved: [...new Set(unresolved)].sort(compareStrings),
|
|
194
|
-
reviewRequired: [...REVIEW_REQUIRED],
|
|
214
|
+
reviewRequired: [...REVIEW_REQUIRED, ...(config.areas.length > 0 ? AREA_REVIEW_REQUIRED : [])],
|
|
215
|
+
};
|
|
216
|
+
const ownership: OwnershipEntry[] = [];
|
|
217
|
+
for (const path of model.files.keys()) {
|
|
218
|
+
if (model.isTest(path)) {
|
|
219
|
+
ownership.push({ path, region: "test", owner: model.testOwner(path) });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const region = model.regionOf(path);
|
|
223
|
+
if (region.kind === "external") continue;
|
|
224
|
+
ownership.push({ path, region: region.kind, owner: region.owner ?? null });
|
|
225
|
+
}
|
|
226
|
+
ownership.sort((a, b) => compareStrings(a.path, b.path));
|
|
227
|
+
return { diagnostics, summary, edges: imports.edges, ownership };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `reports.ownership` for the export (spec/export.md §4.4): the check's file
|
|
232
|
+
* attribution, stamped with the snapshot it was read at, so a consumer can
|
|
233
|
+
* turn a file from a stack frame into a slice, its behaviors and its owner.
|
|
234
|
+
*/
|
|
235
|
+
export function ownershipReport(
|
|
236
|
+
result: ArchitectureResult,
|
|
237
|
+
snapshot: { commit: string | null; graphHash: string },
|
|
238
|
+
): OwnershipReportSection {
|
|
239
|
+
return {
|
|
240
|
+
commit: snapshot.commit,
|
|
241
|
+
graphHash: snapshot.graphHash,
|
|
242
|
+
files: result.ownership.map((entry) => ({ ...entry })),
|
|
243
|
+
withheld: 0,
|
|
195
244
|
};
|
|
196
|
-
return { diagnostics, summary, edges: imports.edges };
|
|
197
245
|
}
|
package/src/claims.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Path and resource ownership (VSA §3, §7; VSA009; profile AMP004).
|
|
3
3
|
*
|
|
4
|
-
* - A nonempty claim that matches no file is VSA009
|
|
4
|
+
* - A nonempty claim that matches no file is VSA009: a warning while the
|
|
5
|
+
* slice is draft (planned, not yet owned), an error once it is not.
|
|
5
6
|
* - Two slices resolving one file is VSA009, once per pair of claims, naming
|
|
6
7
|
* both slices, both claims and the files.
|
|
7
8
|
* - A file claimed by a slice and recorded as a registry resource has two
|
|
@@ -46,13 +47,17 @@ export function checkClaims(model: Model, graph: Graph, registriesPath: string):
|
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
if (slice.claimFiles[i].length === 0) {
|
|
50
|
+
const planned = slice.artifact.meta.status === "draft";
|
|
49
51
|
findings.push(
|
|
50
52
|
finding({
|
|
51
53
|
code: "VSA009",
|
|
54
|
+
...(planned ? { severity: "warning" as const } : {}),
|
|
52
55
|
artifact: slice.id,
|
|
53
56
|
path: slice.path,
|
|
54
57
|
field,
|
|
55
|
-
message:
|
|
58
|
+
message: planned
|
|
59
|
+
? `The ${claim.kind} claim ${claim.path} of draft slice ${slice.id} matches no file yet; it is planned, and an error once the slice leaves draft.`
|
|
60
|
+
: `The ${claim.kind} claim ${claim.path} of ${slice.id} matches no file in the repository.`,
|
|
56
61
|
remediation:
|
|
57
62
|
"Correct the path, add the files it promises, or remove the claim; an empty claim is not ownership (VSA §3).",
|
|
58
63
|
}),
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,21 @@ import { compareStrings, validatePattern } from "./patterns.ts";
|
|
|
11
11
|
|
|
12
12
|
export const ARCHITECTURE_CONFIG_PATH = ".intentset/architecture.yaml";
|
|
13
13
|
|
|
14
|
+
/** One area (profile §9): an Amplify backend of its own and the frontend slices it serves. */
|
|
15
|
+
export interface AreaConfig {
|
|
16
|
+
/** Lowercase name; a slice names its area in `domain`. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Patterns for the area's backend: its own CloudFormation deployment and source API. */
|
|
19
|
+
backend: string[];
|
|
20
|
+
/** The area's schema file, `data/resource.ts`: the one place its models and operations are declared (AMP009). */
|
|
21
|
+
schema: string;
|
|
22
|
+
/** Patterns for the area's frontend slices, such as src/features/<area>/**. May be empty. */
|
|
23
|
+
frontend: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** AppSync joins at most this many source APIs into one Merged API. */
|
|
27
|
+
export const MAX_AREAS = 10;
|
|
28
|
+
|
|
14
29
|
export interface ArchitectureConfig {
|
|
15
30
|
/** Declared scope (VSA §9, Core §11): files outside it are counted, never reported one by one. */
|
|
16
31
|
scope: string[];
|
|
@@ -40,6 +55,12 @@ export interface ArchitectureConfig {
|
|
|
40
55
|
layerMatrix: Record<string, string[]>;
|
|
41
56
|
/** Export conditions tried first when resolving a workspace package's entry, before types, import and default. */
|
|
42
57
|
conditions: string[];
|
|
58
|
+
/** Areas (profile §9). Empty for a repository with one backend, which none of AMP007 to AMP013 concern. */
|
|
59
|
+
areas: AreaConfig[];
|
|
60
|
+
/** Domain-neutral backend code every area may import, which imports no area (AMP008). */
|
|
61
|
+
sharedBackend: string[];
|
|
62
|
+
/** The one frontend file that may import area schemas, type-only, and call generateClient (AMP011). */
|
|
63
|
+
schemaBridge: string | null;
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
export const DEFAULT_ARCHITECTURE_CONFIG: ArchitectureConfig = {
|
|
@@ -63,6 +84,9 @@ export const DEFAULT_ARCHITECTURE_CONFIG: ArchitectureConfig = {
|
|
|
63
84
|
external: ["external", "model"],
|
|
64
85
|
},
|
|
65
86
|
conditions: [],
|
|
87
|
+
areas: [],
|
|
88
|
+
sharedBackend: [],
|
|
89
|
+
schemaBridge: null,
|
|
66
90
|
};
|
|
67
91
|
|
|
68
92
|
const LIST_KEYS = [
|
|
@@ -78,6 +102,7 @@ const LIST_KEYS = [
|
|
|
78
102
|
"tests",
|
|
79
103
|
"backendSdks",
|
|
80
104
|
"conditions",
|
|
105
|
+
"sharedBackend",
|
|
81
106
|
] as const;
|
|
82
107
|
|
|
83
108
|
/** Keys whose entries are path patterns and must pass validatePattern. */
|
|
@@ -93,9 +118,10 @@ const PATTERN_KEYS = new Set<string>([
|
|
|
93
118
|
"ignore",
|
|
94
119
|
"tests",
|
|
95
120
|
"screensGlob",
|
|
121
|
+
"sharedBackend",
|
|
96
122
|
]);
|
|
97
123
|
|
|
98
|
-
const KNOWN_KEYS = [...LIST_KEYS, "screensGlob", "layerMatrix"].sort(compareStrings);
|
|
124
|
+
const KNOWN_KEYS = [...LIST_KEYS, "screensGlob", "layerMatrix", "areas", "schemaBridge"].sort(compareStrings);
|
|
99
125
|
|
|
100
126
|
/** The defaults with a partial configuration laid over them; a key given replaces the default whole. */
|
|
101
127
|
export function resolveConfig(...layers: (Partial<ArchitectureConfig> | undefined)[]): ArchitectureConfig {
|
|
@@ -166,6 +192,25 @@ export function readArchitectureConfig(
|
|
|
166
192
|
} else config.screensGlob = value as string;
|
|
167
193
|
continue;
|
|
168
194
|
}
|
|
195
|
+
if (key === "schemaBridge") {
|
|
196
|
+
const why = typeof value === "string" ? literalPath(value) : "is not a string";
|
|
197
|
+
if (why !== null) {
|
|
198
|
+
report(
|
|
199
|
+
field,
|
|
200
|
+
at(field),
|
|
201
|
+
`schemaBridge ${why}.`,
|
|
202
|
+
"Name the one file, such as src/infrastructure/amplify/client.ts.",
|
|
203
|
+
);
|
|
204
|
+
} else config.schemaBridge = value as string;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (key === "areas") {
|
|
208
|
+
const read = readAreas(value);
|
|
209
|
+
if (typeof read === "string") {
|
|
210
|
+
report(field, at(field), `areas ${read}.`, AREAS_REMEDIATION);
|
|
211
|
+
} else config.areas = read;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
169
214
|
if (key === "layerMatrix") {
|
|
170
215
|
const matrix = readMatrix(value);
|
|
171
216
|
if (matrix === null) {
|
|
@@ -210,3 +255,49 @@ function readMatrix(value: unknown): Record<string, string[]> | null {
|
|
|
210
255
|
}
|
|
211
256
|
return matrix;
|
|
212
257
|
}
|
|
258
|
+
|
|
259
|
+
const AREAS_REMEDIATION =
|
|
260
|
+
"Write each area as a mapping of name, backend (a list of patterns), schema (one file) and frontend (a list of patterns), as profile §9 shows.";
|
|
261
|
+
const AREA_NAME = /^[a-z][a-z0-9-]*$/;
|
|
262
|
+
|
|
263
|
+
/** `areas` as a list of AreaConfig, or why it is not one. */
|
|
264
|
+
function readAreas(value: unknown): AreaConfig[] | string {
|
|
265
|
+
if (!Array.isArray(value)) return "must be a list of areas";
|
|
266
|
+
if (value.length > MAX_AREAS) {
|
|
267
|
+
return `names ${value.length} areas, and an AppSync Merged API joins at most ${MAX_AREAS} source APIs`;
|
|
268
|
+
}
|
|
269
|
+
const areas: AreaConfig[] = [];
|
|
270
|
+
for (const [i, entry] of value.entries()) {
|
|
271
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return `entry ${i} is not a mapping`;
|
|
272
|
+
const record = entry as Record<string, unknown>;
|
|
273
|
+
const unknown = Object.keys(record).filter((key) => !["name", "backend", "schema", "frontend"].includes(key));
|
|
274
|
+
if (unknown.length > 0) return `entry ${i} has the unknown key \`${unknown[0]}\``;
|
|
275
|
+
const { name, backend, schema, frontend = [] } = record;
|
|
276
|
+
if (typeof name !== "string" || !AREA_NAME.test(name)) return `entry ${i} needs a lowercase name such as platform`;
|
|
277
|
+
if (areas.some((area) => area.name === name)) return `name the area ${name} twice`;
|
|
278
|
+
const patterns = (list: unknown, what: string, required: boolean): string[] | string => {
|
|
279
|
+
if (!Array.isArray(list) || !list.every((item) => typeof item === "string" && item !== "")) {
|
|
280
|
+
return `entry ${name} needs ${what} as a list of patterns`;
|
|
281
|
+
}
|
|
282
|
+
if (required && list.length === 0) return `entry ${name} needs at least one ${what} pattern`;
|
|
283
|
+
const bad = (list as string[]).find((pattern) => validatePattern(pattern) !== null);
|
|
284
|
+
return bad === undefined
|
|
285
|
+
? [...(list as string[])]
|
|
286
|
+
: `entry ${name}: the ${what} pattern ${bad} ${validatePattern(bad)}`;
|
|
287
|
+
};
|
|
288
|
+
const backendList = patterns(backend, "backend", true);
|
|
289
|
+
if (typeof backendList === "string") return backendList;
|
|
290
|
+
const frontendList = patterns(frontend, "frontend", false);
|
|
291
|
+
if (typeof frontendList === "string") return frontendList;
|
|
292
|
+
const why = typeof schema === "string" ? literalPath(schema) : "is missing";
|
|
293
|
+
if (why !== null) return `entry ${name}: schema ${why}`;
|
|
294
|
+
areas.push({ name, backend: backendList, schema: schema as string, frontend: frontendList });
|
|
295
|
+
}
|
|
296
|
+
return areas;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Null for one repository-relative file path with no wildcard, or why it is not one. */
|
|
300
|
+
function literalPath(value: string): string | null {
|
|
301
|
+
if (value.includes("*")) return "must name one file, not a pattern";
|
|
302
|
+
return validatePattern(value);
|
|
303
|
+
}
|