Architecture validation service
This container is the runtime side of the webathome.org architecture
metaschema. It hosts the v0.1 JSON Schemas, validates submitted architecture
artifacts via POST /api/validate, and serves the diagram viewer at /viewer/.
Producers (Ansible, the deploy repos, EI, IoT, …) emit one architecture.yaml per
build. Each producer's CI runs arch-validate against this service, fails the
build on non-zero exit, and archives the artifact for the Architecture
pipeline's collector to pick up.
POST /api/validate
POST https://architecture.webathome.org/api/validate
Content-Type: application/json | application/yaml | text/yaml
Body: artifact in the matching format (≤ 5 MiB)
The artifact's top-level schemaVersion selects the validator. v0.1 accepts
only "0.1"; any other value returns 400.
| status | meaning |
|---|---|
200 with {"valid":true,...} |
artifact passes schema + triple matrix |
200 with {"valid":false,"errors":[...]} |
one or more validation errors |
400 |
unparseable body, missing/unknown schemaVersion |
415 |
Content-Type other than the three above |
500 |
server bug |
HTTP status is for request handling. The valid field is the validation
outcome — both pass and fail return 200.
Success
{
"valid": true,
"schemaVersion": "0.1"
}
Failure
Each error is normalised to a single LLM-friendly shape. The path is a JSON
Pointer into the submitted artifact, line is the 1-based line it sits on in
the submitted text (the key's line for a mapping value; absent when the pointer
does not map to a node, e.g. through a << merge), the value is extracted at
that path so the reader doesn't have to re-fetch it, and schemaUrl points at
the most specific schema or enum file relevant to the error.
{
"valid": false,
"schemaVersion": "0.1",
"errors": [
{
"path": "/nodes/0/id",
"line": 5,
"keyword": "pattern",
"message": "value 'Node_BadId' does not match the required pattern /^node:[a-z][a-z0-9-]*,[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/",
"value": "Node_BadId",
"schemaUrl": "https://architecture.webathome.org/schema/v0.1/generated/node.schema.json",
"hint": "see the schema's pattern for the exact rule"
}
]
}
A type error on a scalar quotes the scalar as written next to what the YAML
1.2 reader made of it, so a string that parsed as a number is visible as such:
firmware: 9e10234 (parsed as float Infinity) is not of expected type string.
A long or multi-line scalar is cut to its first line, at most 60 characters,
ending in ….
The service also enforces the ArchiMate 3.2 (source-kind, type, target-kind)
matrix on every relation whose endpoints are present in the submitted
artifact. Cross-producer references (ids not present locally) are skipped —
the Architecture pipeline's collector enforces them at merge time. Matrix
violations are reported with keyword: "x-allowedTriples".
Merged-dataset endpoints
The Architecture pipeline runs the federation collector (tooling/collect.py)
during the image build, merges every registered producer's last-successful
architecture.yaml into a single consolidated dataset, and bakes the three
output files into the container. The validation service serves them
verbatim from disk.
| URL | content |
|---|---|
/data/v0.1/architecture.yaml |
merged dataset (YAML) — every element kind, every relation, derived groupings and capabilityRealizations maps |
/data/v0.1/architecture.json |
same content, canonical JSON |
/data/v0.1/validation-report.json |
summary + warnings[] + divergences[] for the last successful pipeline run |
Errors fail the pipeline before emission, so the report never carries
errors — only non-fatal observations (deprecated-target references,
alias-hint divergence across producers, etc.). The Jenkinsfile also
archives validation-report.json as a Jenkins build artifact for
historical traceability.
Schema URLs
Every schema is served at both .yaml (the canonical authoring form) and
.json (the canonical JSON Schema form). Use whichever your tooling prefers.
| URL | content |
|---|---|
/schema/v0.1/architecture.schema.yaml |
top-level artifact envelope |
/schema/v0.1/subset.schema.yaml |
meta-schema for subset.yaml |
/schema/v0.1/generated/<kind>.schema.yaml |
per-kind element schemas (node, applicationcomponent, …) |
/schema/v0.1/generated/relations.schema.yaml |
relation schema + x-allowedTriples matrix |
/schema/v0.1/enums/capabilities.yaml |
curated capability catalogue |
/schema/v0.1/enums/lifecycle-states.yaml |
lifecycle enum |
/schema/v0.1/enums/environments.yaml |
environment enum |
/schema/v0.1/archimate/archimate3_Model.xsd |
vendored ArchiMate 3.2 XSD |
/schema/v0.1/archimate/relationships.xml |
Archi relationship matrix (source) |
All responses set Access-Control-Allow-Origin: * and
Cache-Control: public, max-age=300.
End-to-end curl example
cat > artifact.yaml <<'EOF'
schemaVersion: "0.1"
producer: my-repo
systemSoftware:
- id: ss:my-daemon,3f1b9c40-0e2a-4d11-9f7a-2c6b1e8a4d90
label: My daemon
summary: Long-running service owned by my-repo.
introduced: 2026-05-27
lifecycle: active
EOF
curl -sS \
-H 'Content-Type: application/yaml' \
--data-binary @artifact.yaml \
https://architecture.webathome.org/api/validate \
| jq .
arch-validate CLI
The dev-facing artifact. Producers run it from the aac-tools toolchain, the
registry:5000/aac-tools image ArgoCDTools publishes: in Jenkins through the
JenkinsPipelineUtils architectureProducer steps, which run it in the
toolchain's container (see the producer manual's Jenkins integration), in a
KubeCoder environment that declares the toolchain through cexec aac-tools. A
producer repo keeps no copy of the script.
# from a producer repo's KubeCoder environment
cexec aac-tools arch-validate architecture.yaml
cexec aac-tools arch-validate dev.architecture.yaml prd.architecture.yaml
cat architecture.yaml | cexec aac-tools arch-validate - # stdin
cexec aac-tools arch-validate --json architecture.yaml # raw endpoint JSON
cexec aac-tools arch-validate --quiet architecture.yaml # suppress OK lines
Each error prints as its path, then its message — prefixed line N: when
the error carries a line — then its schemaUrl and any hint.
Exit codes: 0 valid, 1 invalid, 2 transport/server error.
Override the endpoint for local testing:
cexec aac-tools env ARCHITECTURE_VALIDATE_URL=http://localhost:8080/api/validate \
arch-validate artifact.yaml
The toolchain ships this repo's .claude/architecture/arch-validate.py byte for
byte: ArgoCDTools vendors it as aac-tools/image/arch-validate.py, and a test
there pins its md5. A change here reaches producers when ArgoCDTools takes the
new copy and republishes the image.
$schema pragma
For editor / IDE schema-completion (VS Code's YAML extension, IntelliJ's JSON Schema mappings, etc.), reference the envelope schema at the top of your artifact:
# yaml-language-server: $schema=https://architecture.webathome.org/schema/v0.1/architecture.schema.json
schemaVersion: "0.1"
producer: my-repo
# …
The schema URL is stable; only the v0.1 immutability rule applies.
Keeping an artifact current
The architecture in a producer repo is kept current centrally, from the
Architecture repo, rather than by the producer repo itself. On demand, a tool
there clones each registered repo, judges whether the commits since its
architecture sources last changed — or since that repo was last reviewed,
whichever is later — change what the architecture must say, and where they do
runs a headless session in the clone that edits the artifact and commits. Those
commits are pushed to the repo's default branch, and the builds the push starts
are followed to their end. A repo whose AaC job builds a promotion branch, such
as a deploy repo's prd, which only fast-forwards along the default branch,
gets the same push; its artifact picks the edit up when that branch is promoted.
A generated producer is also judged on the gaps its generator reports: the
gap: <what> lines of its last successful AaC build. A gap no run has judged
yet goes to the editing session even when no commit is new, and that session is
handed every gap the build reports, whatever the commits.
Nothing is installed in a producer repo: the agents that do the editing are copied into the throwaway clone for the run, and the repo is not asked to invoke anything itself.
An optional .architecturerc at the producer repo's root steers that run:
generated: false # true for a generated producer
sources: [":(glob)**/docs/architecture/**"] # git pathspecs the architecture is built from
instructions: | # handed verbatim to the sessions
Model the worker pool as one component.
All three keys are optional: generated and sources default to the values
shown, instructions to nothing. Any other key is an error. instructions wins
over the agents' own guidance on anything specific to the repo. A generated
producer does need the file: generated: true is what keeps the session in the
sources the artifact is generated from, instead of the artifact it would
otherwise edit.
Schema-change requests
To add a capability id, a relation kind, a stereotype, or any other vocabulary
extension: open a PR against this repo
(pvginkel/Architecture) editing
the relevant file under schema/v0.1/:
- New capability →
schema/v0.1/enums/capabilities.yaml - New element kind / new attribute on an existing kind →
schema/v0.1/subset.yaml(the generator rebuildsschema/v0.1/generated/*.yaml) - New allowed relation triple → adjust
schema/v0.1/subset.yaml's subset of the ArchiMate matrix; the generator regeneratesrelations.schema.yaml.
After editing, run the generator and validator locally:
cd tooling && poetry run python generate.py
cd tooling && poetry run python validate.py meta
cd tooling && poetry run python validate.py ../schema/v0.1/examples/valid-full.yaml
CI on this repo runs the same checks plus the service's vitest suite.