@archrad/deterministic
v0.1.5
Published
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 L
Downloads
785
Maintainers
Readme
@archrad/deterministic

Your architecture drifts before you write a single line of code. archrad validate catches it — deterministically, in CI, before the PR merges.
Define your system as a graph. ArchRAD compiles it, lints it against architecture rules, and tells you exactly what's wrong — with rule codes, not opinions.
Quick start (60 seconds)
npm install -g @archrad/deterministic
archrad validate --ir fixtures/demo-direct-db-violation.jsonYou should see something like (exact wording may vary slightly by version):
⚠️ IR-LINT-DIRECT-DB-ACCESS-002: API node "orders-api" connects directly to datastore node "orders-db"
Fix: Introduce a service or domain layer between HTTP handlers and persistence.For a smaller graph (single endpoint, no DB edge), try fixtures/minimal-graph.json — you will get different warnings (e.g. health/auth heuristics), not IR-LINT-DIRECT-DB-ACCESS-002.
No IR file yet? Cold-start from an existing OpenAPI spec:
archrad ingest openapi --spec ./openapi.yaml --out ./graph.json
archrad validate --ir ./graph.jsonWhat it does
ArchRAD is a blueprint compiler and governance layer. You define your architecture as an IR — nodes, edges, allowed connections — and ArchRAD validates it against a deterministic rule engine. The same IR, the same rules, the same inputs always produce the same findings.
| Command | What it checks | Codes |
|---------|---------------|-------|
| archrad validate | Graph structure + architecture lint | IR-STRUCT-* IR-LINT-* |
| archrad validate-drift | IR vs generated code on disk | DRIFT-* |
| archrad ingest openapi | Derive IR from existing OpenAPI spec | — |
| archrad export | Compile IR → FastAPI or Express + Docker | — |
CI integration
# Fail on any structural error (default):
archrad validate --ir ./graph.json
# Also fail on lint warnings:
archrad validate --ir ./graph.json --fail-on-warning
# Machine-readable output for GitHub Actions:
archrad validate --ir ./graph.json --jsonMCP server (Cursor / Claude Desktop)
After install, archrad-mcp is on your PATH. Add it to your IDE:
{
"mcpServers": {
"archrad": { "command": "archrad-mcp" }
}
}Your agent can call the same engine as the CLI via six MCP tools (e.g. archrad_validate_ir, archrad_lint_summary, archrad_validate_drift, archrad_policy_packs_load, archrad_list_rule_codes, archrad_suggest_fix). See docs/MCP.md for parameters and local testing.
How it works (architecture)
IR (nodes/edges) → validateIrStructural (IR-STRUCT-*) → errors block export
↓
validateIrLint (IR-LINT-*) → warnings (CI: --fail-on-warning / --max-warnings)
↓
pythonFastAPI | nodeExpress generators
↓
openapi.yaml + app code + package metadata
↓
golden layer (Dockerfile, docker-compose.yml, Makefile, README; host→container e.g. 8080:8080)
↓
validateOpenApiInBundleStructural(openapi.yaml) → document-shape warnings (not full API lint)
↓
{ files, openApiStructuralWarnings, irStructuralFindings, irLintFindings }
Optional CI: archrad validate-drift → re-export IR in-memory, diff vs existing ./out → DRIFT-MISSING / DRIFT-MODIFIED (thin deterministic gate)Validation levels (quick contract)
- JSON Schema validation — IR document shape vs
schemas/archrad-ir-graph-v1.schema.json(editor/CI; optional at runtime). - IR structural validation —
validateIrStructural: arrays, ids, HTTPconfig, edge refs, cycles (IR-STRUCT-*). Uses an internal normalized graph (seedocs/IR_CONTRACT.md). - Export-time generated OpenAPI structural validation — Parse + required fields on the generated
openapi.yaml(document shape, not Spectral).
Architecture lint (IR-LINT-*) sits after structural checks: rule visitors on the parsed graph (heuristics, not schema).
Validation layers (naming)
| Layer (OSS) | What it is | Codes |
|-------------|------------|--------|
| IR structural validation | Graph well-formedness: ids, edges, cycles, HTTP path/method | IR-STRUCT-* |
| Architecture lint (basic) | Deterministic heuristics only (no AI, no org policy) | IR-LINT-* |
| OpenAPI structural validation (document shape) | Parse + required top-level OpenAPI fields on generated spec | (string warnings, not IR codes) |
| Layer (Cloud — not this package) | Examples | |----------------------------------|----------| | Policy engine | SOC2, org rules, entitlement | | Architecture intelligence | Deeper NFR / cost / security reasoning | | AI remediation | Repair loops, suggested edits |
- IR structural validation: duplicate/missing node ids, bad HTTP
config.url/config.method, unknown edge endpoints, directed cycles. - Architecture lint: Implemented as a registry of visitor functions on a parsed graph (
buildParsedLintGraph→LINT_RULE_REGISTRYinsrc/lint-rules.ts). If the IR cannot be parsed,buildParsedLintGraphreturns{ findings }(IR-STRUCT-) instead ofnull; useisParsedLintGraph()or callvalidateIrLint, which forwards those findings. Each rule returnsIrStructuralFinding[];runArchitectureLinting/validateIrLintflatten them. Custom org rules: composerunArchitectureLintingwith your own(g) => findingsin CI (worked example:docs/CUSTOM_RULES.md), or fork and append toLINT_RULE_REGISTRYif the stockarchrad validateCLI must emit your codes. CLIarchrad validate/archrad exportprint lint under **Architecture lint (IR-LINT-)** (grouped separately from structural). Codes include IR-LINT-DIRECT-DB-ACCESS-002, IR-LINT-SYNC-CHAIN-001, IR-LINT-NO-HEALTHCHECK-003, IR-LINT-HIGH-FANOUT-004, IR-LINT-ISOLATED-NODE-005, IR-LINT-DUPLICATE-EDGE-006, IR-LINT-HTTP-MISSING-NAME-007, IR-LINT-DATASTORE-NO-INCOMING-008, IR-LINT-MULTIPLE-HTTP-ENTRIES-009, IR-LINT-MISSING-AUTH-010, IR-LINT-DEAD-NODE-011. Sync-chain depth counts synchronous edges only; mark message/queue/async hops viaedge.metadata.protocol/config.async(seeedgeRepresentsAsyncBoundaryinlint-graph.tsanddocs/ENGINEERING_NOTES.md). - Generators →
openapi.yaml, handlers, deps. - Golden path →
make run/docker compose up --build. - OpenAPI document shape on the bundle — not Spectral-level lint. Issues →
openApiStructuralWarnings.
IR contract: schemas/archrad-ir-graph-v1.schema.json. Parser boundary + normalized shapes: docs/IR_CONTRACT.md (normalizeIrGraph → materializeNormalizedGraph).
Trust builder: IR-STRUCT-* errors block export; IR-LINT-* warnings are visible and can gate CI via --fail-on-warning / --max-warnings; OpenAPI shape issues surface as export warnings.
Reference (OSS): docs/DRIFT.md (deterministic validate-drift), docs/RULE_CODES.md (finding codes; MCP docsUrl targets GitHub anchors), docs/MCP.md (MCP tools + local testing).
Codegen vs validation (retry, timeouts, policy)
Generators may emit retry/timeout/circuit-breaker code when the IR carries matching edge or node config (e.g. retryPolicy). That is code generation, not a guarantee. OSS does not currently require or lint “every external call must have timeout/retry” — that class of rule is semantic / policy and fits ArchRad Cloud or custom linters on top of the IR.
Ways to use it
| Mode | Best for | Example |
|------|-----------|---------|
| CLI | Quick local scaffolding, CI, “no Node project” usage | archrad export --ir graph.json --target python --out ./out |
| YAML → IR | Author graphs in YAML, emit JSON for validate/export | archrad yaml-to-ir -y graph.yaml -o graph.json |
| OpenAPI → IR | Derive HTTP nodes from OpenAPI 3.x (same IR shape as YAML path); ArchRad Cloud merge uses the same library | archrad ingest openapi --spec openapi.yaml -o graph.json |
| CLI validate | CI / pre-commit: IR structural + architecture lint, no codegen | archrad validate --ir graph.json |
| CLI validate-drift | After export or merges: on-disk tree vs fresh deterministic export from same IR | archrad validate-drift -i graph.json -t python -o ./out |
| Library (@archrad/deterministic) | IDPs / pipelines | runDeterministicExport → files + findings; runValidateDrift / runDriftCheckAgainstFiles for drift |
| MCP (archrad-mcp) | Cursor / Claude Desktop / other MCP hosts | stdio server: validate IR, lint summary, drift, policy packs, static archrad_suggest_fix — see docs/MCP.md |
MCP (Cursor example): after npm i -g @archrad/deterministic (or npx), add a server with command archrad-mcp and no args (stdio). Pass ir inline or irPath to a JSON file for large graphs. archrad_suggest_fix returns curated text for a finding code (e.g. IR-LINT-MISSING-AUTH-010) — not machine-generated IR patches. Step-by-step testing (smoke script, MCP Inspector, Cursor chat prompts): docs/MCP.md, section Local testing.
CLI
Input is structured IR (JSON), not natural language. There is no archrad export --prompt "...". Pass a graph file (nodes/edges).
Fixtures (in this repo): fixtures/minimal-graph.json (small); fixtures/demo-direct-db-violation.json / fixtures/demo-direct-db-layered.json (before/after IR-LINT-DIRECT-DB-ACCESS-002); fixtures/ecommerce-with-warnings.json (many lint rules); fixtures/payment-retry-demo.json (retry-related codegen in export). --target python is the FastAPI bundle; there is no separate fastapi target. To go from plain English → IR, use ArchRad Cloud or your own LLM step; this package only does IR → files.
Recording demos and GIFs (VHS, storyboards, drift replay): scripts/README_DEMO_RECORDING.md only — not required to use the CLI.
OpenAPI → JSON (spec as source of truth): each operation under paths becomes an http node (config.url + config.method). Then validate and export like any other IR:
archrad ingest openapi --spec ./openapi.yaml --out ./graph.json
archrad validate --ir ./graph.json
archrad export --ir ./graph.json --target python --out ./outOpenAPI 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).
YAML → JSON (lighter authoring): edit fixtures/minimal-graph.yaml (or your own file) and compile to IR JSON, then validate or export:
archrad yaml-to-ir --yaml fixtures/minimal-graph.yaml --out ./graph.json
archrad validate --ir ./graph.json
# or pipe: archrad yaml-to-ir -y fixtures/minimal-graph.yaml | archrad validate --ir /dev/stdin # on Unix; on Windows use --out then validateYAML must have either top-level graph: (object) or top-level nodes: (array); bare graphs are wrapped as { "graph": { ... } } automatically.
After npm install -g or npx (typical):
archrad export --ir ./graph.json --target node --out ./my-express-api
# Validate IR (structural + architecture lint). Pretty output; exit 1 on structural errors by default:
archrad validate --ir ./graph.json
# Machine-readable + CI gates:
archrad validate --ir ./graph.json --json
archrad validate --ir ./graph.json --fail-on-warning
archrad validate --ir ./graph.json --max-warnings 0
# Structural only (skip IR-LINT-*):
archrad validate --ir ./graph.json --skip-lint
# Declarative PolicyPack YAML/JSON in a directory (after IR-LINT-*; skipped with --skip-lint):
archrad validate --ir ./graph.json --policies ./policy-packsFrom a git clone (contributors): run npm ci && npm run build in the package root (there is no prepare hook — see docs/ENGINEERING_NOTES.md), then use node dist/cli.js the same way you would use archrad (e.g. node dist/cli.js validate --ir fixtures/minimal-graph.json).
Deterministic drift (thin, OSS): compare an existing export tree on disk to a fresh export from the same IR. Detects missing / changed generated files (line endings normalized). Optional --strict-extra flags files present on disk but not in the reference export. Not semantic “does code match intent” — ArchRad Cloud adds builder/UI drift checks and broader governance.
archrad export -i ./graph.json -t python -o ./out
# …edit files under ./out…
archrad validate-drift -i ./graph.json -t python -o ./out --skip-host-port-check
# CI-friendly:
archrad validate-drift -i ./graph.json -t python -o ./out --skip-host-port-check --json
# Fail if the tree has extra files not in the reference export:
archrad validate-drift -i ./graph.json -t python -o ./out --strict-extraExample: validate architecture
archrad validate --ir fixtures/minimal-graph.jsonExample output (stderr):
archrad validate:
⚠️ IR-LINT-NO-HEALTHCHECK-003: No HTTP node exposes a typical health/readiness path (...)
Fix: Add a GET route such as /health for orchestrators and load balancers.
Suggestion: Expose liveness vs readiness separately if your platform distinguishes them.
Impact: Weaker deploy/rollback safety and harder operations automation.Structural errors look like ❌ IR-STRUCT-... with Fix: lines. Use --json to consume findings in GitHub Actions or other CI.
--ir— JSON:{ "graph": { "nodes", "edges", "metadata" } }or a raw graph (CLI wraps it).--target—python|node|nodejs--out— output directory (created if needed)--host-port <n>— host port Docker publishes (default 8080; container still listens on 8080 inside). Same as envARCHRAD_HOST_PORT.--skip-host-port-check— don’t probe127.0.0.1before export.--strict-host-port— exit with error if the host port appears in use (CI-friendly).--danger-skip-ir-structural-validation— UNSAFE: skipvalidateIrStructuralbefore export (never in CI). Parse/normalize failures (invalid root, empty graph) are still detected viavalidateIrLintand block export withIR-STRUCT-*inirStructuralFindings. A hidden--skip-ir-structural-validationremains as a deprecated alias.--skip-ir-lint— skipvalidateIrLintduring export.--fail-on-warning/--max-warnings <n>— if set, no files are written when IR structural + lint findings violate the policy (same semantics asvalidate).
By default, if 8080 (or your --host-port) looks busy on localhost, the CLI warns so you can change the port before docker compose fails with a bind error.
Export runs IR structural validation, then architecture lint, then codegen. Structural errors abort with no files written. irLintFindings contains only IR-LINT-*; IR-STRUCT-* from a failed parse always appear under irStructuralFindings (including when structural validation was skipped). Lint warnings print by default; use --fail-on-warning / --max-warnings to block writes for CI.
Library
import {
runDeterministicExport,
runValidateDrift,
validateIrStructural,
validateIrLint,
sortFindings,
shouldFailFromFindings,
} from '@archrad/deterministic';
const { files, openApiStructuralWarnings, irStructuralFindings, irLintFindings } =
await runDeterministicExport(ir, 'python', {
hostPort: 8080,
skipIrLint: false, // default
});
// Structural errors → empty files (unless skipIrStructuralValidation). Lint is non-blocking for export unless you check policy in your pipeline.
const drift = await runValidateDrift(ir, 'python', '/path/to/existing-export', {
skipIrLint: false, // set true to match CLI --skip-ir-lint on reference export
});
// drift.ok, drift.driftFindings, drift.exportResult — same core semantics as CLI validate-drift (CLI also probes host port before calling the library)
const all = sortFindings([...validateIrStructural(ir), ...validateIrLint(ir)]);
if (shouldFailFromFindings(all, { failOnWarning: true })) {
/* gate your CI */
}Optional: isLocalHostPortFree / normalizeGoldenHostPort from the same package if you want your own preflight.
Golden path (contributors — local clone)
This path assumes you cloned the repo and ran npm ci && npm run build in the package root. If you only installed with npm install -g @archrad/deterministic, use archrad instead of node dist/cli.js (same flags).
node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./out
cd ./out
make run
# In another terminal, once the API is up:
curl -sS -X POST http://localhost:8080/signup -H "Content-Type: application/json" -d '{}'You should see 422 Unprocessable Entity (FastAPI/Pydantic) or 400 with a clear body — proof the stack is live and validation matches the spec, not a silent 500.
Quick check from a clone: cd packages/deterministic && npm ci && npm run build && npm test, then export to ./tmp-out, cd tmp-out && make run, curl as above. Use --host-port 18080 (or node dist/cli.js export ... --host-port 18080) if 8080 is busy.
Optional: bash scripts/golden-path-demo.sh runs the same flow. Demo recording (GIFs, tapes, drift replays): scripts/README_DEMO_RECORDING.md.
Open source vs ArchRad Cloud
This repository is only the deterministic engine — local, offline, no phone-home.
| Here (OSS) | ArchRad Cloud (commercial product) |
|------------|-------------------------------------|
| IR structural + architecture lint (validate, IR-STRUCT-*, IR-LINT-*), compiler (export), validate-drift (on-disk vs fresh export), OpenAPI document-shape warnings, golden Docker/Makefile | Policy engine, deeper architecture intelligence, AI remediation, richer drift / sync UX in the builder |
| archrad CLI forever, no account required for this package | Auth, orgs, quotas, billing |
| No proprietary LLM orchestration or “repair” loops | LLM generation, repair, multi-model routing |
| No Git sync, no enterprise policy injection in this repo | Git push, governance, compliance dashboards |
You can depend on this CLI and library without ArchRad Cloud. The cloud product stacks collaboration and AI on top of the same deterministic contract.
Contributing
See CONTRIBUTING.md.
License
Apache-2.0 — see LICENSE.
