
Read-only architecture contract: deterministic resolution of manifests into artifact and documentation candidates across workspace, Git, Apicurio, Artifactory, and Maven; schema validation, variable interpolation, ordered fallbacks.
ZenWave Manifest is the read-only architecture contract shared by ZenWave tools. A hand-authored zenwave-architecture.yml describes domains, services, documentation, and artifacts; the Kotlin Multiplatform library resolves that content deterministically from workspace, Git, Apicurio Registry, generic Artifactory, or Maven sources.
https://schemas.zenwave360.io/zenwave-architecture/1.0/schema.json
https://schemas.zenwave360.io/zenwave-architecture/latest/schema.json
# schema-test: valid
# yaml-language-server: $schema=https://schemas.zenwave360.io/zenwave-architecture/1.0/schema.json
config:
title: Arcadia architecture
groupIdExpression: "io.arcadia.${service.id}"
contentResolution:
- workspace
- git
- apicurio
- artifactory
- maven
sources:
workspace:
basePathExpression: "../../${domain.id}/${subdomain.id}/${service.id}"
git:
provider: gitlab
server: https://gitlab.com
apicurio:
server: https://registry.example.com
artifactory:
server: https://artifacts.example.com
maven:
provider: artifactory
server: https://artifacts.example.com/artifactory
repository: maven-releases
domains:
commerce:
version: 1.0.0
services:
orders:
id: orders-api
docs:
summary: docs/SUMMARY.md
catalog: docs/EVENT_CATALOG.md
readme: docs/README.md
artifacts:
- type: openapi
path: contracts/orders.openapi.yaml
version: 1.1.0
subdomains:
fulfillment:
version: 2.0.0
services:
shipping:
artifacts:
- type: asyncapi
path: contracts/shipping.asyncapi.yamlDomains, subdomains, and services use a non-blank explicit id when present and otherwise use their YAML map key. A direct service has an empty subdomain.id. The primary example deliberately omits artifact name and artifactId: names are never derived from paths, while artifact IDs use their configured fallback.
For a manifest stored at /work/architecture/manifests/zenwave-architecture.yml, the ../../ prefix reaches /work; the first artifact's workspace candidate is:
/work/commerce/orders-api/contracts/orders.openapi.yaml
The same artifact has these remote candidates:
https://gitlab.com/commerce/orders-api/-/raw/v1.1.0/contracts/orders.openapi.yaml
https://registry.example.com/apis/registry/v3/groups/orders-api/artifacts/contracts%2Forders.openapi.yaml/versions/1.1.0/content
https://artifacts.example.com/artifactory/contracts/commerce/orders-api/1.1.0/contracts/orders.openapi.yaml
https://artifacts.example.com/artifactory/maven-releases/io/arcadia/orders-api/orders.openapi/1.1.0/orders.openapi-1.1.0.jar!/contracts/orders.openapi.yaml
The empty direct-service subdomain segment is removed without damaging https://.
The Artifactory URL above comes from its preset contentUrlExpression; declare the field only when the repository key or layout differs from artifactory/contracts/....
Static config.properties substitution runs first. It cannot override canonical values or source-local ${server}. Runtime interpolation is literal: no functions, conditionals, nested expressions, or fallback syntax are supported.
| Variable | Value |
|---|---|
${domain.id} |
Explicit domain ID, otherwise its map key. |
${subdomain.id} |
Explicit subdomain ID, otherwise its map key; empty for direct services. |
${service.id} |
Explicit service ID, otherwise its map key. |
${service.repository} |
Explicit service repository name; unresolved when omitted. |
${domain.version} |
Explicit domain version, when declared. |
${subdomain.version} |
Explicit subdomain version, when declared. |
${service.version} |
Explicit service version, when declared. |
${artifact.version} |
Explicit artifact version, when declared. |
${artifact.path} |
Complete declared artifact path. |
${artifact.name} |
Explicit non-blank artifact name only; unresolved when omitted. |
${artifact.fileName} |
Final artifact path segment, with every extension. |
${artifact.fileNameWithoutExtension} |
Filename with only its final extension removed. |
${content.path} |
Current artifact path or selected document path. |
${service.docs[key]} |
Declared document path for the literal key. |
${groupId} |
Common resolved group coordinate. |
${artifactId} |
Common resolved artifact coordinate. |
${version} |
Inherited content version. |
Service architecture identity and source-control location are deliberately separate. Repository identity is never inferred from service.id:
config:
sources:
workspace:
basePathExpression: "../${service.repository}"
git:
provider: github
server: https://github.com
contentUrlExpression: "${server}/arcadia-editions/${service.repository}/raw/main/${content.path}"
domains:
catalog:
services:
products:
id: catalog.catalog-management.catalog-products
repository: catalog-products-apiIf repository is omitted, ${service.repository} stays unresolved and any selected expression that uses it fails with the standard unresolved-runtime-variable diagnostic.
Filename behavior is exact:
| Path | artifact.fileName |
artifact.fileNameWithoutExtension |
|---|---|---|
contracts/orders.openapi.yaml |
orders.openapi.yaml |
orders.openapi |
archive.tar.gz |
archive.tar.gz |
archive.tar |
domain-model.zdl |
domain-model.zdl |
domain-model |
.gitignore |
.gitignore |
.gitignore |
README |
README |
README |
Documents are selected from the service map. Keys may contain letters, numbers, ., _, and -:
${service.docs[summary]} = docs/SUMMARY.md
${service.docs[catalog]} = docs/EVENT_CATALOG.md
${service.docs[readme]} = docs/README.md
${service.docs} alone, an invalid lookup, or a missing key fails before I/O. Loading all docs evaluates the source URL once per entry, so ${content.path} changes from docs/SUMMARY.md to docs/EVENT_CATALOG.md and then docs/README.md.
${version} preserves effective-version inheritance: artifact, service, subdomain, then domain. Documents begin at service. The qualified version expressions expose only their corresponding explicit declaration and do not inherit. YAML strings and numbers normalize to strings; config.version and artifact contents never supply a content version.
Common coordinates resolve as:
groupId = service.groupId, otherwise config.groupIdExpression
artifactId = artifact.artifactId, otherwise config.artifactIdExpression
Defaults are ${service.id} and ${artifact.fileNameWithoutExtension}. Coordinate expressions cannot recursively reference ${groupId} or ${artifactId}. Overrides are valid:
domains:
commerce:
services:
orders:
groupId: io.arcadia.orders
artifacts:
- name: public-orders-api
artifactId: orders-openapi
type: openapi
path: contracts/orders.openapi.yaml${groupId} and ${artifactId} can be selected by any artifact provider expression, not only Maven.
contentResolution defaults to [workspace]. Only listed sources participate; configuring a source does not activate it. Transport and not-found failures advance to the next applicable source. Apicurio and Maven are artifact-only and are skipped for service documents. A preferred source may reorder or restrict only active sources.
Supported absolute URIs bypass the list. Exhausted reads report redacted candidates in attempted order and the final error. Unknown sources, active unconfigured sources, invalid providers, missing required fields, and unresolved expressions fail deterministically before transport I/O where possible.
| Source | Artifacts | Service docs |
|---|---|---|
workspace |
Yes | Yes |
git |
Yes | Yes |
apicurio |
Yes | No |
artifactory |
Yes | Yes |
maven |
Yes | No |
See the detailed source guide for provider defaults, custom Git recipes, encoding, workspace traversal, Artifactory layouts, and Maven Central JAR extraction.
manifest-core contains common parsing, diagnostics, interpolation, and candidate construction for JVM and JavaScript. JVM also provides Maven Central JAR entry extraction; JavaScript hosts can supply an archive-entry loader.
Published library coordinates are io.zenwave360.manifest:manifest-core, manifest-core-jvm, and manifest-core-js.
Run:
./gradlew :manifest-core:jvmTest
The suite validates both schemas and every complete README YAML block marked schema-test: valid. Release workflow details are in RELEASING.md; this repository is licensed under the MIT License.
ZenWave Manifest is the read-only architecture contract shared by ZenWave tools. A hand-authored zenwave-architecture.yml describes domains, services, documentation, and artifacts; the Kotlin Multiplatform library resolves that content deterministically from workspace, Git, Apicurio Registry, generic Artifactory, or Maven sources.
https://schemas.zenwave360.io/zenwave-architecture/1.0/schema.json
https://schemas.zenwave360.io/zenwave-architecture/latest/schema.json
# schema-test: valid
# yaml-language-server: $schema=https://schemas.zenwave360.io/zenwave-architecture/1.0/schema.json
config:
title: Arcadia architecture
groupIdExpression: "io.arcadia.${service.id}"
contentResolution:
- workspace
- git
- apicurio
- artifactory
- maven
sources:
workspace:
basePathExpression: "../../${domain.id}/${subdomain.id}/${service.id}"
git:
provider: gitlab
server: https://gitlab.com
apicurio:
server: https://registry.example.com
artifactory:
server: https://artifacts.example.com
maven:
provider: artifactory
server: https://artifacts.example.com/artifactory
repository: maven-releases
domains:
commerce:
version: 1.0.0
services:
orders:
id: orders-api
docs:
summary: docs/SUMMARY.md
catalog: docs/EVENT_CATALOG.md
readme: docs/README.md
artifacts:
- type: openapi
path: contracts/orders.openapi.yaml
version: 1.1.0
subdomains:
fulfillment:
version: 2.0.0
services:
shipping:
artifacts:
- type: asyncapi
path: contracts/shipping.asyncapi.yamlDomains, subdomains, and services use a non-blank explicit id when present and otherwise use their YAML map key. A direct service has an empty subdomain.id. The primary example deliberately omits artifact name and artifactId: names are never derived from paths, while artifact IDs use their configured fallback.
For a manifest stored at /work/architecture/manifests/zenwave-architecture.yml, the ../../ prefix reaches /work; the first artifact's workspace candidate is:
/work/commerce/orders-api/contracts/orders.openapi.yaml
The same artifact has these remote candidates:
https://gitlab.com/commerce/orders-api/-/raw/v1.1.0/contracts/orders.openapi.yaml
https://registry.example.com/apis/registry/v3/groups/orders-api/artifacts/contracts%2Forders.openapi.yaml/versions/1.1.0/content
https://artifacts.example.com/artifactory/contracts/commerce/orders-api/1.1.0/contracts/orders.openapi.yaml
https://artifacts.example.com/artifactory/maven-releases/io/arcadia/orders-api/orders.openapi/1.1.0/orders.openapi-1.1.0.jar!/contracts/orders.openapi.yaml
The empty direct-service subdomain segment is removed without damaging https://.
The Artifactory URL above comes from its preset contentUrlExpression; declare the field only when the repository key or layout differs from artifactory/contracts/....
Static config.properties substitution runs first. It cannot override canonical values or source-local ${server}. Runtime interpolation is literal: no functions, conditionals, nested expressions, or fallback syntax are supported.
| Variable | Value |
|---|---|
${domain.id} |
Explicit domain ID, otherwise its map key. |
${subdomain.id} |
Explicit subdomain ID, otherwise its map key; empty for direct services. |
${service.id} |
Explicit service ID, otherwise its map key. |
${service.repository} |
Explicit service repository name; unresolved when omitted. |
${domain.version} |
Explicit domain version, when declared. |
${subdomain.version} |
Explicit subdomain version, when declared. |
${service.version} |
Explicit service version, when declared. |
${artifact.version} |
Explicit artifact version, when declared. |
${artifact.path} |
Complete declared artifact path. |
${artifact.name} |
Explicit non-blank artifact name only; unresolved when omitted. |
${artifact.fileName} |
Final artifact path segment, with every extension. |
${artifact.fileNameWithoutExtension} |
Filename with only its final extension removed. |
${content.path} |
Current artifact path or selected document path. |
${service.docs[key]} |
Declared document path for the literal key. |
${groupId} |
Common resolved group coordinate. |
${artifactId} |
Common resolved artifact coordinate. |
${version} |
Inherited content version. |
Service architecture identity and source-control location are deliberately separate. Repository identity is never inferred from service.id:
config:
sources:
workspace:
basePathExpression: "../${service.repository}"
git:
provider: github
server: https://github.com
contentUrlExpression: "${server}/arcadia-editions/${service.repository}/raw/main/${content.path}"
domains:
catalog:
services:
products:
id: catalog.catalog-management.catalog-products
repository: catalog-products-apiIf repository is omitted, ${service.repository} stays unresolved and any selected expression that uses it fails with the standard unresolved-runtime-variable diagnostic.
Filename behavior is exact:
| Path | artifact.fileName |
artifact.fileNameWithoutExtension |
|---|---|---|
contracts/orders.openapi.yaml |
orders.openapi.yaml |
orders.openapi |
archive.tar.gz |
archive.tar.gz |
archive.tar |
domain-model.zdl |
domain-model.zdl |
domain-model |
.gitignore |
.gitignore |
.gitignore |
README |
README |
README |
Documents are selected from the service map. Keys may contain letters, numbers, ., _, and -:
${service.docs[summary]} = docs/SUMMARY.md
${service.docs[catalog]} = docs/EVENT_CATALOG.md
${service.docs[readme]} = docs/README.md
${service.docs} alone, an invalid lookup, or a missing key fails before I/O. Loading all docs evaluates the source URL once per entry, so ${content.path} changes from docs/SUMMARY.md to docs/EVENT_CATALOG.md and then docs/README.md.
${version} preserves effective-version inheritance: artifact, service, subdomain, then domain. Documents begin at service. The qualified version expressions expose only their corresponding explicit declaration and do not inherit. YAML strings and numbers normalize to strings; config.version and artifact contents never supply a content version.
Common coordinates resolve as:
groupId = service.groupId, otherwise config.groupIdExpression
artifactId = artifact.artifactId, otherwise config.artifactIdExpression
Defaults are ${service.id} and ${artifact.fileNameWithoutExtension}. Coordinate expressions cannot recursively reference ${groupId} or ${artifactId}. Overrides are valid:
domains:
commerce:
services:
orders:
groupId: io.arcadia.orders
artifacts:
- name: public-orders-api
artifactId: orders-openapi
type: openapi
path: contracts/orders.openapi.yaml${groupId} and ${artifactId} can be selected by any artifact provider expression, not only Maven.
contentResolution defaults to [workspace]. Only listed sources participate; configuring a source does not activate it. Transport and not-found failures advance to the next applicable source. Apicurio and Maven are artifact-only and are skipped for service documents. A preferred source may reorder or restrict only active sources.
Supported absolute URIs bypass the list. Exhausted reads report redacted candidates in attempted order and the final error. Unknown sources, active unconfigured sources, invalid providers, missing required fields, and unresolved expressions fail deterministically before transport I/O where possible.
| Source | Artifacts | Service docs |
|---|---|---|
workspace |
Yes | Yes |
git |
Yes | Yes |
apicurio |
Yes | No |
artifactory |
Yes | Yes |
maven |
Yes | No |
See the detailed source guide for provider defaults, custom Git recipes, encoding, workspace traversal, Artifactory layouts, and Maven Central JAR extraction.
manifest-core contains common parsing, diagnostics, interpolation, and candidate construction for JVM and JavaScript. JVM also provides Maven Central JAR entry extraction; JavaScript hosts can supply an archive-entry loader.
Published library coordinates are io.zenwave360.manifest:manifest-core, manifest-core-jvm, and manifest-core-js.
Run:
./gradlew :manifest-core:jvmTest
The suite validates both schemas and every complete README YAML block marked schema-test: valid. Release workflow details are in RELEASING.md; this repository is licensed under the MIT License.