@archrad/deterministic 0.1.2 → 0.1.3
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 +19 -1
- package/README.md +2 -0
- package/dist/cli.js +32 -19
- package/dist/graphPredicates.d.ts +3 -2
- package/dist/graphPredicates.d.ts.map +1 -1
- package/dist/graphPredicates.js +3 -2
- package/dist/lint-rules.d.ts.map +1 -1
- package/dist/lint-rules.js +5 -13
- package/fixtures/e2e-no-security-openapi.yaml +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.3] - 2026-04-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **CLI** (`validate`, `export`, `validate-drift`): a missing **`--ir`** file now reports **`archrad: --ir file not found: <path>`** instead of **`invalid JSON`**.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`IR-LINT-MISSING-AUTH-010`** — HTTP entry detection uses **`ParsedLintGraph.inDegree`** (same counts as `buildParsedLintGraph`) instead of a separate scan; reverse adjacency for auth-as-gateway only includes edges whose endpoints exist in **`nodeById`**.
|
|
19
|
+
- **Docs:** README documents **OpenAPI security → IR → `IR-LINT-MISSING-AUTH-010`** for the spec-to-compliance workflow.
|
|
20
|
+
- **`graphPredicates.ts`:** clarified comments for **`isHttpEndpointType`** vs **`isHttpLikeType`** (`graphql` vs `gateway` / `bff` / `grpc`).
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Tests:** structural **`IR-STRUCT-HTTP_*`** coverage for **`graphql`** (validated) vs **`gateway`** (excluded); regression tests locking lint message substrings for **`IR-LINT-DEAD-NODE-011`**, **`IR-LINT-DIRECT-DB-ACCESS-002`**, **`IR-LINT-SYNC-CHAIN-001`** (terminal copy / Show HN).
|
|
25
|
+
- **Fixture** **`fixtures/e2e-no-security-openapi.yaml`** (OpenAPI with **no** `security` / `securitySchemes`) + test asserting **`openApiStringToCanonicalIr` → `validateIrLint` → `IR-LINT-MISSING-AUTH-010`** — same pipeline as **`archrad ingest openapi`** + **`archrad validate`**.
|
|
26
|
+
|
|
10
27
|
## [0.1.2] - 2026-03-28
|
|
11
28
|
|
|
12
29
|
### Fixed
|
|
@@ -71,7 +88,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
71
88
|
- Documented **codegen vs validation** for retry/timeout IR fields and **InkByte vs OSS** scope in README and structural/semantic doc.
|
|
72
89
|
- README positioning: **deterministic compiler and linter for system architecture**; validation layers table (OSS vs Cloud); **`validate-drift`**, drift GIF / trust-loop recording docs, library **`runValidateDrift`** example.
|
|
73
90
|
|
|
74
|
-
[Unreleased]: https://github.com/archradhq/arch-deterministic/compare/v0.1.
|
|
91
|
+
[Unreleased]: https://github.com/archradhq/arch-deterministic/compare/v0.1.3...HEAD
|
|
92
|
+
[0.1.3]: https://github.com/archradhq/arch-deterministic/releases/tag/v0.1.3
|
|
75
93
|
[0.1.2]: https://github.com/archradhq/arch-deterministic/releases/tag/v0.1.2
|
|
76
94
|
[0.1.1]: https://github.com/archradhq/arch-deterministic/releases/tag/v0.1.1
|
|
77
95
|
[0.1.0]: https://github.com/archradhq/arch-deterministic/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -105,6 +105,8 @@ archrad validate --ir ./graph.json
|
|
|
105
105
|
archrad export --ir ./graph.json --target python --out ./out
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
**OpenAPI security → IR → lint:** ingestion copies **global** and **per-operation** `security` requirement names onto each HTTP node as `config.security` (sorted, deterministic). An operation with explicit `security: []` becomes `config.authRequired: false` (intentionally public). If the spec declares **no** security at any level, nodes are left without those fields — then **`archrad validate`** can surface **`IR-LINT-MISSING-AUTH-010`** on HTTP-like entry nodes (compliance gap from the spec artifact alone).
|
|
109
|
+
|
|
108
110
|
**YAML → JSON (lighter authoring):** edit **`fixtures/minimal-graph.yaml`** (or your own file) and compile to IR JSON, then validate or export:
|
|
109
111
|
|
|
110
112
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,30 @@ async function writeTree(baseDir, files) {
|
|
|
21
21
|
await writeFile(dest, content, 'utf8');
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/** Read and parse IR JSON; distinguish missing file from invalid JSON. */
|
|
25
|
+
async function readIrJsonFromPath(irPath) {
|
|
26
|
+
let raw;
|
|
27
|
+
try {
|
|
28
|
+
raw = await readFile(irPath, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
const err = e;
|
|
32
|
+
if (err?.code === 'ENOENT') {
|
|
33
|
+
console.error(`archrad: --ir file not found: ${irPath}`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.error(`archrad: could not read --ir file: ${irPath} (${err?.message ?? String(e)})`);
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
console.error('archrad: invalid JSON in --ir file');
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
24
48
|
function parseMaxWarnings(v) {
|
|
25
49
|
if (v == null || v === '')
|
|
26
50
|
return undefined;
|
|
@@ -48,12 +72,8 @@ program
|
|
|
48
72
|
.option('--max-warnings <n>', 'Exit with error if warning count is greater than n (e.g. 0 allows no warnings)')
|
|
49
73
|
.action(async (cmdOpts) => {
|
|
50
74
|
const irPath = resolve(cmdOpts.ir);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
ir = JSON.parse(await readFile(irPath, 'utf8'));
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
console.error('archrad: invalid JSON in --ir file');
|
|
75
|
+
const ir = await readIrJsonFromPath(irPath);
|
|
76
|
+
if (ir == null) {
|
|
57
77
|
process.exitCode = 1;
|
|
58
78
|
return;
|
|
59
79
|
}
|
|
@@ -182,16 +202,12 @@ program
|
|
|
182
202
|
.action(async (cmdOpts) => {
|
|
183
203
|
const irPath = resolve(cmdOpts.ir);
|
|
184
204
|
const outDir = resolve(cmdOpts.out);
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
ir = JSON.parse(raw);
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
console.error('archrad: invalid JSON in --ir file');
|
|
205
|
+
const parsed = await readIrJsonFromPath(irPath);
|
|
206
|
+
if (parsed == null) {
|
|
192
207
|
process.exitCode = 1;
|
|
193
208
|
return;
|
|
194
209
|
}
|
|
210
|
+
const ir = parsed;
|
|
195
211
|
const actualIR = ir.graph ? ir : { graph: ir };
|
|
196
212
|
const hostPort = normalizeGoldenHostPort(cmdOpts.hostPort ?? process.env.ARCHRAD_HOST_PORT);
|
|
197
213
|
if (!cmdOpts.skipHostPortCheck) {
|
|
@@ -266,15 +282,12 @@ program
|
|
|
266
282
|
.action(async (cmdOpts) => {
|
|
267
283
|
const irPath = resolve(cmdOpts.ir);
|
|
268
284
|
const outDir = resolve(cmdOpts.out);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
ir = JSON.parse(await readFile(irPath, 'utf8'));
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
console.error('archrad: invalid JSON in --ir file');
|
|
285
|
+
const parsed = await readIrJsonFromPath(irPath);
|
|
286
|
+
if (parsed == null) {
|
|
275
287
|
process.exitCode = 1;
|
|
276
288
|
return;
|
|
277
289
|
}
|
|
290
|
+
const ir = parsed;
|
|
278
291
|
const actualIR = ir.graph ? ir : { graph: ir };
|
|
279
292
|
const hostPort = normalizeGoldenHostPort(cmdOpts.hostPort ?? process.env.ARCHRAD_HOST_PORT);
|
|
280
293
|
if (!cmdOpts.skipHostPortCheck) {
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Narrow predicate: node types that carry a single HTTP endpoint (`config.url` + HTTP method).
|
|
6
6
|
* Used by **structural validation** for `IR-STRUCT-HTTP_PATH` / `IR-STRUCT-HTTP_METHOD` checks.
|
|
7
|
-
* `
|
|
7
|
+
* **`http` / `https` / `rest` / `api` / `graphql`** share this contract in the IR (GraphQL is one route + method in the IR model).
|
|
8
|
+
* **`gateway`**, **`grpc`**, and **`bff`** are intentionally **excluded** — they use different config shapes
|
|
8
9
|
* (upstream routing, proto service/method, multi-route aggregation) and must not be required
|
|
9
|
-
* to supply a REST-style url + HTTP method.
|
|
10
|
+
* to supply a REST-style `url` + HTTP method; they remain **`isHttpLikeType`** for lint (entries, health, sync chain, etc.).
|
|
10
11
|
*/
|
|
11
12
|
export declare function isHttpEndpointType(t: string): boolean;
|
|
12
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphPredicates.d.ts","sourceRoot":"","sources":["../src/graphPredicates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH
|
|
1
|
+
{"version":3,"file":"graphPredicates.d.ts","sourceRoot":"","sources":["../src/graphPredicates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAQjD;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAM/C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CASrD;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAStD"}
|
package/dist/graphPredicates.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Narrow predicate: node types that carry a single HTTP endpoint (`config.url` + HTTP method).
|
|
6
6
|
* Used by **structural validation** for `IR-STRUCT-HTTP_PATH` / `IR-STRUCT-HTTP_METHOD` checks.
|
|
7
|
-
* `
|
|
7
|
+
* **`http` / `https` / `rest` / `api` / `graphql`** share this contract in the IR (GraphQL is one route + method in the IR model).
|
|
8
|
+
* **`gateway`**, **`grpc`**, and **`bff`** are intentionally **excluded** — they use different config shapes
|
|
8
9
|
* (upstream routing, proto service/method, multi-route aggregation) and must not be required
|
|
9
|
-
* to supply a REST-style url + HTTP method.
|
|
10
|
+
* to supply a REST-style `url` + HTTP method; they remain **`isHttpLikeType`** for lint (entries, health, sync chain, etc.).
|
|
10
11
|
*/
|
|
11
12
|
export function isHttpEndpointType(t) {
|
|
12
13
|
const s = String(t ?? '')
|
package/dist/lint-rules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint-rules.d.ts","sourceRoot":"","sources":["../src/lint-rules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAcvD,8HAA8H;AAC9H,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CA6B5E;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAkBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAwDpF;AAED,iCAAiC;AACjC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAsB3E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAoB1E;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CA0B3E;AAED,oCAAoC;AACpC,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAmB7E;AAED,wCAAwC;AACxC,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAkBjF;AAED,2HAA2H;AAC3H,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAkBjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"lint-rules.d.ts","sourceRoot":"","sources":["../src/lint-rules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAcvD,8HAA8H;AAC9H,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CA6B5E;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAkBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAwDpF;AAED,iCAAiC;AACjC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAsB3E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAoB1E;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CA0B3E;AAED,oCAAoC;AACpC,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAmB7E;AAED,wCAAwC;AACxC,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAkBjF;AAED,2HAA2H;AAC3H,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAkBjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAoD7E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAsBtE;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,eAAe,KAAK,mBAAmB,EAAE,CAY3F,CAAC;AAEF,gGAAgG;AAChG,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,eAAe,GAAG,mBAAmB,EAAE,CAEhF"}
|
package/dist/lint-rules.js
CHANGED
|
@@ -284,23 +284,15 @@ export function ruleMultipleHttpEntries(g) {
|
|
|
284
284
|
* for intentionally public endpoints (health, public assets, etc.).
|
|
285
285
|
*/
|
|
286
286
|
export function ruleHttpMissingAuth(g) {
|
|
287
|
-
const { edges, nodeById, adj } = g;
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
for (const e of edges) {
|
|
291
|
-
if (!e || typeof e !== 'object')
|
|
292
|
-
continue;
|
|
293
|
-
const { to } = edgeEndpoints(e);
|
|
294
|
-
if (to)
|
|
295
|
-
hasIncomingEdge.add(to);
|
|
296
|
-
}
|
|
297
|
-
// Build reverse adjacency: to → [from] for auth-coverage check #2
|
|
287
|
+
const { edges, nodeById, adj, inDegree } = g;
|
|
288
|
+
// Entry = no valid incoming edge (same counts as buildParsedLintGraph.inDegree)
|
|
289
|
+
// Build reverse adjacency: to → [from] for auth-coverage check #2 (valid endpoints only, same as adj)
|
|
298
290
|
const reverseAdj = new Map();
|
|
299
291
|
for (const e of edges) {
|
|
300
292
|
if (!e || typeof e !== 'object')
|
|
301
293
|
continue;
|
|
302
294
|
const { from, to } = edgeEndpoints(e);
|
|
303
|
-
if (!from || !to)
|
|
295
|
+
if (!from || !to || !nodeById.has(from) || !nodeById.has(to))
|
|
304
296
|
continue;
|
|
305
297
|
if (!reverseAdj.has(to))
|
|
306
298
|
reverseAdj.set(to, []);
|
|
@@ -310,7 +302,7 @@ export function ruleHttpMissingAuth(g) {
|
|
|
310
302
|
for (const [id, n] of nodeById) {
|
|
311
303
|
if (!isHttpLikeType(nodeType(n)))
|
|
312
304
|
continue;
|
|
313
|
-
if (
|
|
305
|
+
if ((inDegree.get(id) ?? 0) > 0)
|
|
314
306
|
continue; // not an entry node
|
|
315
307
|
const cfg = (n.config ?? {});
|
|
316
308
|
// Explicit opt-out: config.authRequired === false marks an intentionally public endpoint
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archrad/deterministic",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A deterministic compiler and linter for system architecture. Validate your architecture before you write code. OSS: structural validation + basic architecture lint (rule-based); FastAPI/Express export; OpenAPI document-shape; golden Docker/Makefile — no LLM.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"archrad",
|