@zenwave360/architecture-manifest 0.0.0-stage → 1.0.0-next.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZenWave's Authors and Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,475 @@
1
- # Temporary Holding Version
1
+ # ZenWave Architecture
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![Maven Central](https://img.shields.io/maven-central/v/io.zenwave360.architecture/architecture-manifest.svg?label=Maven%20Central&logo=apachemaven)](https://search.maven.org/artifact/io.zenwave360.architecture/architecture-manifest)
4
+ [![build](https://github.com/ZenWave360/zenwave-architecture/actions/workflows/publish-maven-snapshots.yml/badge.svg?branch=develop)](https://github.com/ZenWave360/zenwave-architecture/actions/workflows/publish-maven-snapshots.yml)
5
+ [![line coverage](https://raw.githubusercontent.com/ZenWave360/zenwave-architecture/badges/coverage.svg)](https://github.com/ZenWave360/zenwave-architecture/actions/workflows/main.yml)
6
+ [![branch coverage](https://raw.githubusercontent.com/ZenWave360/zenwave-architecture/badges/branches.svg)](https://github.com/ZenWave360/zenwave-architecture/actions/workflows/main.yml)
7
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ZenWave360/zenwave-architecture/blob/main/LICENSE)
8
+
9
+ ZenWave Architecture is the architecture contract shared by ZenWave tools. A hand-authored Architecture Manifest (`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. Its source-aware editor can update existing scalar values without reserializing the surrounding YAML.
10
+
11
+
12
+ - Versioned schema: `https://schemas.zenwave360.io/zenwave-architecture/1.0/schema.json`
13
+ - Latest schema: `https://schemas.zenwave360.io/zenwave-architecture/latest/schema.json`
14
+ - Detailed authoring guide: [docs/source-resolution-guide.md](docs/source-resolution-guide.md)
15
+ - Normative source-resolution specification: [docs/source-resolution-specification.md](docs/source-resolution-specification.md)
16
+ - Workspace diagnostic codes: [docs/diagnostics.md](docs/diagnostics.md)
17
+
18
+ ## Complete manifest
19
+
20
+ ```yaml
21
+ # schema-test: valid
22
+ # yaml-language-server: $schema=https://schemas.zenwave360.io/zenwave-architecture/1.0/schema.json
23
+
24
+ config:
25
+ title: Arcadia architecture
26
+ groupIdExpression: "io.arcadia.${service.id}"
27
+
28
+ contentResolution:
29
+ - workspace
30
+ - git
31
+ - apicurio
32
+ - artifactory
33
+ - maven
34
+
35
+ sources:
36
+ workspace:
37
+ basePathExpression: "../../${domain.id}/${subdomain.id}/${service.id}"
38
+
39
+ git:
40
+ provider: gitlab
41
+ server: https://gitlab.com
42
+
43
+ apicurio:
44
+ server: https://registry.example.com
45
+
46
+ artifactory:
47
+ server: https://artifacts.example.com
48
+
49
+ maven:
50
+ provider: artifactory
51
+ server: https://artifacts.example.com/artifactory
52
+ repository: maven-releases
53
+
54
+ domains:
55
+ commerce:
56
+ version: 1.0.0
57
+
58
+ services:
59
+ orders:
60
+ id: orders-api
61
+ docs:
62
+ summary: docs/SUMMARY.md
63
+ catalog: docs/EVENT_CATALOG.md
64
+ readme: docs/README.md
65
+ artifacts:
66
+ - type: openapi
67
+ path: contracts/orders.openapi.yaml
68
+ version: 1.1.0
69
+
70
+ subdomains:
71
+ fulfillment:
72
+ version: 2.0.0
73
+ services:
74
+ shipping:
75
+ artifacts:
76
+ - type: asyncapi
77
+ path: contracts/shipping.asyncapi.yaml
78
+ version: 2.0.0
79
+ ```
80
+
81
+ Domains, 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`. Every artifact declares its own required `version`; domain, subdomain, and service versions are optional and apply only to service documents. The primary example deliberately omits artifact `name` and `artifactId`: names are never derived from paths, while artifact IDs use their configured fallback.
82
+
83
+ For a manifest stored at `/work/architecture/manifests/zenwave-architecture.yml`, the `../../` prefix reaches `/work`; the first artifact's workspace candidate is:
84
+
85
+ ```text
86
+ /work/commerce/orders-api/contracts/orders.openapi.yaml
87
+ ```
88
+
89
+ The same artifact has these remote candidates:
90
+
91
+ ```text
92
+ https://gitlab.com/commerce/orders-api/-/raw/v1.1.0/contracts/orders.openapi.yaml
93
+ https://registry.example.com/apis/registry/v3/groups/orders-api/artifacts/contracts%2Forders.openapi.yaml/versions/1.1.0/content
94
+ https://artifacts.example.com/artifactory/contracts/commerce/orders-api/1.1.0/contracts/orders.openapi.yaml
95
+ https://artifacts.example.com/artifactory/maven-releases/io/arcadia/orders-api/contracts%2Forders.openapi/1.1.0/contracts%2Forders.openapi-1.1.0.jar!/contracts/orders.openapi.yaml
96
+ ```
97
+
98
+ The empty direct-service subdomain segment is removed without damaging `https://`.
99
+ The Artifactory URL above comes from its preset `contentUrlExpression`; declare the field only when the repository key or layout differs from `artifactory/contracts/...`.
100
+
101
+ ## Runtime values
102
+
103
+ 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.
104
+
105
+ | Variable | Value |
106
+ | --- | --- |
107
+ | `${owner.id}` | Resolved ID of the service or domain that declares the artifact. |
108
+ | `${owner.repository}` | Explicit repository of the service or domain artifact owner; unresolved when omitted. |
109
+ | `${domain.id}` | Explicit domain ID, otherwise its map key. |
110
+ | `${subdomain.id}` | Explicit subdomain ID, otherwise its map key; empty for direct services. |
111
+ | `${service.id}` | Explicit service ID, otherwise its map key. |
112
+ | `${service.repository}` | Explicit service repository name; unresolved when omitted. |
113
+ | `${domain.version}` | Explicit domain version, when declared. |
114
+ | `${subdomain.version}` | Explicit subdomain version, when declared. |
115
+ | `${service.version}` | Explicit service version, when declared. |
116
+ | `${artifact.version}` | Declared artifact version; artifact `version` is required. |
117
+ | `${artifact.path}` | Complete declared artifact path. |
118
+ | `${artifact.pathWithoutExtension}` | Complete artifact path with only its final extension removed. |
119
+ | `${artifact.name}` | Explicit non-blank artifact name only; unresolved when omitted. |
120
+ | `${artifact.fileName}` | Final artifact path segment, with every extension. |
121
+ | `${artifact.fileNameWithoutExtension}` | Filename with only its final extension removed. |
122
+ | `${content.path}` | Current artifact path or selected document path. |
123
+ | `${service.docs[key]}` | Declared document path for the literal key. |
124
+ | `${groupId}` | Common resolved group coordinate. |
125
+ | `${artifactId}` | Common resolved artifact coordinate. |
126
+ | `${version}` | Effective content version: `artifact.version` for an artifact, the inherited service, subdomain, or domain version for a service document. |
127
+
128
+ Services and domains may own artifacts. Owner identity and source-control location are deliberately separate; repository identity is never inferred from `owner.id`:
129
+
130
+ ```yaml
131
+ config:
132
+ sources:
133
+ workspace:
134
+ basePathExpression: "../${service.repository}"
135
+ git:
136
+ provider: github
137
+ server: https://github.com
138
+ contentUrlExpression: "${server}/arcadia-editions/${service.repository}/raw/main/${content.path}"
139
+ maven:
140
+ provider: github
141
+ server: https://maven.pkg.github.com
142
+ repository: "arcadia-editions/${service.repository}"
143
+ domains:
144
+ catalog:
145
+ services:
146
+ products:
147
+ id: catalog.catalog-management.catalog-products
148
+ repository: catalog-products-api
149
+ ```
150
+
151
+ `maven.repository` accepts the same runtime expressions and is resolved once per artifact, so services publishing to their own GitHub Packages repositories share a single Maven source declaration.
152
+
153
+ If `repository` is omitted, `${service.repository}` stays unresolved and any selected expression that uses it fails with the standard unresolved-runtime-variable diagnostic.
154
+
155
+ Filename behavior is exact:
156
+
157
+ | Path | `artifact.pathWithoutExtension` | `artifact.fileName` | `artifact.fileNameWithoutExtension` |
158
+ | --- | --- | --- | --- |
159
+ | `contracts/orders.openapi.yaml` | `contracts/orders.openapi` | `orders.openapi.yaml` | `orders.openapi` |
160
+ | `archive.tar.gz` | `archive.tar` | `archive.tar.gz` | `archive.tar` |
161
+ | `domain-model.zdl` | `domain-model` | `domain-model.zdl` | `domain-model` |
162
+ | `.gitignore` | `.gitignore` | `.gitignore` | `.gitignore` |
163
+ | `README` | `README` | `README` | `README` |
164
+
165
+ Documents are selected from the service map. Keys may contain letters, numbers, `.`, `_`, and `-`:
166
+
167
+ ```text
168
+ ${service.docs[summary]} = docs/SUMMARY.md
169
+ ${service.docs[catalog]} = docs/EVENT_CATALOG.md
170
+ ${service.docs[readme]} = docs/README.md
171
+ ```
172
+
173
+ `${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`.
174
+
175
+ `${version}` resolves differently for the two kinds of content. For an artifact it is exactly `artifact.version`, which is a required field and never inherits from the service, subdomain, or domain. For a service document it inherits service, then subdomain, then domain. The qualified version expressions expose only their corresponding explicit declaration and do not inherit. Blank versions count as absent. YAML strings and numbers normalize to strings; `config.version` and artifact contents never supply a content version.
176
+
177
+ ## Coordinates and explicit overrides
178
+
179
+ Common coordinates resolve as:
180
+
181
+ ```text
182
+ groupId = owner.groupId, otherwise config.groupIdExpression
183
+ artifactId = artifact.artifactId, otherwise config.artifactIdExpression
184
+ ```
185
+
186
+ Defaults are `${owner.id}` and `${artifact.pathWithoutExtension}`. Coordinate expressions cannot recursively reference `${groupId}` or `${artifactId}`. Overrides are valid:
187
+
188
+ ```yaml
189
+ domains:
190
+ commerce:
191
+ services:
192
+ orders:
193
+ groupId: io.arcadia.orders
194
+ artifacts:
195
+ - name: public-orders-api
196
+ artifactId: orders-openapi
197
+ type: openapi
198
+ path: contracts/orders.openapi.yaml
199
+ version: 1.1.0
200
+ ```
201
+
202
+ `${groupId}` and `${artifactId}` can be selected by any artifact provider expression, not only Maven.
203
+
204
+ ## Artifact catalog and selections
205
+
206
+ `ManifestArtifactCatalog` resolves every declared artifact together with its owner and effective
207
+ coordinates. Declared artifact `type` values remain an open string namespace: `openapi`,
208
+ `asyncapi`, `zdl`, `zfl`, `grpc`, and future types require no registration with the library.
209
+
210
+ ### Consumer artifact references
211
+
212
+ A service may identify the exact artifact that consumes one of its contracts with
213
+ `service.id#artifact.id`. The artifact selector is matched against the effective artifact ID first,
214
+ including `config.artifactIdExpression`; `type:<type>` explicitly selects every artifact of a type.
215
+ Plain selectors fall back to an artifact type only when exactly one artifact matches.
216
+
217
+ ```yaml
218
+ # schema-test: valid
219
+ domains:
220
+ catalog:
221
+ services:
222
+ inventory:
223
+ artifacts:
224
+ - type: asyncapi
225
+ path: asyncapi.yml
226
+ version: 1.0.0
227
+ consumers:
228
+ - orders.checkout#asyncapi-client
229
+ orders:
230
+ services:
231
+ checkout:
232
+ id: orders.checkout
233
+ artifacts:
234
+ - type: asyncapi-client
235
+ path: asyncapi-client.yml
236
+ version: 1.0.0
237
+ ```
238
+
239
+ `ManifestConsumerIndex` resolves these declarations in both directions and infers compatible
240
+ provider artifacts from `ManifestConsumptionRules`. Suffix-less service references remain accepted
241
+ for backwards compatibility but do not create artifact-level consumption edges.
242
+
243
+ ## Semantic architecture graph
244
+
245
+ The `architecture-graph` Kotlin Multiplatform module builds a typed, source-aware graph from a resolved
246
+ manifest and its artifacts. It runs on JVM and Node/JS and deliberately sits above `architecture-manifest`:
247
+ manifest loading remains format-agnostic, while the graph module depends on `dsl-kotlin` for its
248
+ built-in ZDL and ZFL analyzers.
249
+
250
+ ```kotlin
251
+ val result = ArchitectureGraph.build(manifest, loader)
252
+ val graph = result.graph
253
+
254
+ val callers = graph.incoming(methodId, ArchitectureEdgeKind.INVOKES)
255
+ val emittedEvents = graph.outgoing(methodId, ArchitectureEdgeKind.EMITS)
256
+ val consumers = graph.consumersOf(channelId)
257
+ val flowProjection = graph.subgraphFrom(flowId, maxDepth = 5)
258
+ ```
259
+
260
+ The initial built-ins contribute:
261
+
262
+ - manifest domain, subdomain, service, and artifact nodes;
263
+ - declared artifact-level consumption plus verified AsyncAPI channel and operation consumption;
264
+ - ZDL API, service, method, entity, and event nodes, including REST/AsyncAPI evidence and API references;
265
+ - ZFL system, flow, start, step, and event nodes, including triggers, outcome handlers, emissions,
266
+ system-to-ZDL declarations, and resolved ZFL-step-to-ZDL-method invocations;
267
+ - cross-artifact links from ZDL methods and events to AsyncAPI channel nodes.
268
+
269
+ Canonical concept nodes use location-independent `zw:` IDs. Artifact-derived representation nodes
270
+ retain their resolved source URI, owner reference, artifact ID, semantic path, and source coordinates,
271
+ and connect to concepts through `REPRESENTS` edges. Concepts and relationships aggregate typed
272
+ evidence with confidence. Loading and analysis failures become diagnostics so one broken artifact
273
+ does not discard the rest of the graph.
274
+
275
+ JVM callers that do not use coroutines can use `BlockingArchitectureGraph.build(...)` or
276
+ `BlockingArchitectureGraphBuilder`. Additional artifact formats remain an open namespace: provide
277
+ one or more `ManifestGraphArtifactAnalyzer` implementations to `ArchitectureGraphBuilder`; matching
278
+ analyzers share the already-loaded artifact content and their contributions are merged. AsyncAPI
279
+ classification and operation-level consumption are supplied by the typed, catalog-agnostic
280
+ `ManifestApiConsumptions` API in `architecture-manifest`; graph and presentation layers project that evidence
281
+ without maintaining their own YAML matching engines.
282
+
283
+ Callers can resolve either a unique effective artifact ID or every artifact of a declared type on
284
+ one owner:
285
+
286
+ ```kotlin
287
+ val owner = ManifestOwnerSelector.Repository("orders-api")
288
+ val openApis = catalog.resolveByType(owner, "openapi").artifacts
289
+ val contract = catalog.resolveByArtifactId(owner, "contracts/orders").artifact
290
+ ```
291
+
292
+ Type resolution also supports library-defined virtual behavior. `asyncapi-all` selects every
293
+ declared `asyncapi` and `asyncapi-client` artifact on exactly one service and returns the concrete
294
+ members; it is never inserted into `ZenWaveManifest.artifacts`.
295
+
296
+ ```kotlin
297
+ val catalog = ManifestArtifactCatalog.resolve(manifest, loader)
298
+ val selection = catalog.resolve(
299
+ ManifestArtifactSelector.typeInRepository(
300
+ "orders-api",
301
+ ManifestArtifactSelector.ASYNCAPI_ALL,
302
+ ),
303
+ )
304
+ val bundleMembers: List<ResolvedManifestArtifact> = selection.artifacts
305
+ ```
306
+
307
+ Repository selectors may generally span several owners. An artifact-ID selector must resolve one
308
+ artifact. A declared type must resolve artifacts on one owner, while `asyncapi-all` must resolve one
309
+ matching service; missing and ambiguous selections fail with `ManifestArtifactSelectionException`.
310
+
311
+ ## Workspace query runtime
312
+
313
+ The `workspace-runtime` Kotlin Multiplatform module exposes the adapter-neutral query contract used
314
+ by MCP, Web, CLI, and future LSP integrations. It provides bounded overview, deterministic semantic
315
+ ID resolution, browse, search, relationship traversal, provenance, and diagnostic operations.
316
+ Manifest `aliases` on domains, subdomains, and services redirect historical `zw:` IDs, including
317
+ descendant IDs after a parent rename.
318
+
319
+ The exact reverse-provenance `at` query crosses from an editor's document URI and semantic path to
320
+ the canonical architecture concept. LSP integrations use this operation for `conceptAt`; they do
321
+ not inspect `ArchitectureGraph` or construct `zw:` IDs themselves.
322
+
323
+ ```kotlin
324
+ val workspace: WorkspaceQueryApi = WorkspaceRuntime(loader).open(manifestUri)
325
+ val service = workspace.resolve(SemanticId("zw:sales/ordering/order-service"))
326
+ val dependencies = workspace.related(
327
+ SemanticId("zw:sales/ordering/order-service"),
328
+ direction = Direction.OUTGOING,
329
+ confidence = listOf("DECLARED", "RESOLVED", "INFERRED_EXACT"),
330
+ )
331
+ val conceptsAtCursor = workspace.at(
332
+ WorkspaceSourceLocation("file:///workspace/orders/model.zdl", "events.OrderPlaced"),
333
+ )
334
+ ```
335
+
336
+ The `workspace-mcp` Kotlin Multiplatform module maps the seven tools from the Workspace
337
+ specification onto this same interface. `WorkspaceMcpTools.tools` supplies bounded JSON schemas and
338
+ `WorkspaceMcpTools.call(...)` returns a readable summary plus structured content for direct MCP SDK
339
+ registration.
340
+
341
+ Validate a workspace before starting an adapter:
342
+
343
+ ```text
344
+ ./gradlew :workspace-cli:installDist
345
+ workspace-cli/build/install/zenwave/bin/zenwave validate /workspace/zenwave-architecture.yml
346
+ ```
347
+
348
+ ## Source-aware editing
349
+
350
+ `ZenWaveManifestEditor.updateArtifactVersions` accepts one or more artifact-ID or type selectors.
351
+ It snapshots the root and referenced documents, resolves every selection, patches only the existing
352
+ version scalar ranges, and validates the complete overlay before returning document changes.
353
+
354
+ ```kotlin
355
+ val result = editor.updateArtifactVersions(
356
+ manifestUri,
357
+ listOf(
358
+ ManifestArtifactVersionUpdate(
359
+ ManifestArtifactSelector.typeInRepository(
360
+ "orders-api",
361
+ ManifestArtifactSelector.ASYNCAPI_ALL,
362
+ ),
363
+ "1.4.0",
364
+ ),
365
+ ),
366
+ )
367
+ ```
368
+
369
+ The returned `ManifestDocumentTextUpdate` values preserve unrelated formatting, comments, scalar
370
+ quote style, and source ownership through external `$ref` documents. The editor does not persist
371
+ them; callers can compare `originalText` before writing. JVM and Java callers can use
372
+ `BlockingZenWaveManifestEditor` with the default file and HTTP(S) document reader.
373
+
374
+ `updateScalars` is the lower-level API for updating another existing owner or artifact scalar.
375
+ Structural edits such as adding or removing manifest nodes remain out of scope.
376
+
377
+ ## Ordered reads
378
+
379
+ `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.
380
+
381
+ 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.
382
+
383
+ | Source | Artifacts | Service docs |
384
+ | --- | --- | --- |
385
+ | `workspace` | Yes | Yes |
386
+ | `git` | Yes | Yes |
387
+ | `apicurio` | Yes | No |
388
+ | `artifactory` | Yes | Yes |
389
+ | `maven` | Yes | No |
390
+
391
+ See [the detailed source guide](docs/source-resolution-guide.md) for provider defaults, custom Git recipes, encoding, workspace traversal, Artifactory layouts, and Maven Central JAR extraction.
392
+
393
+ ## Loading API
394
+
395
+ `ZenWaveManifestLoader` is the canonical multiplatform API. Its loading operations are
396
+ `suspend` functions so JVM and Node.js/Kotlin callers can perform manifest, document, and
397
+ artifact I/O without blocking their server event loop:
398
+
399
+ ```kotlin
400
+ val loader = ZenWaveManifestLoader()
401
+ val manifest = loader.load("file:///workspace/zenwave-architecture.yml") // http also work
402
+ val service = manifest.findService("commerce/orders")!!
403
+ val openApis = service.findArtifacts("openapi")
404
+ val text = loader.loadArtifactText(manifest, service, openApis.first())
405
+ val schema = loader.resolveArtifactReference(
406
+ manifest,
407
+ service,
408
+ openApis.first(),
409
+ "schemas/order.yaml",
410
+ )
411
+ ```
412
+
413
+ Java and synchronous JVM integrations can use `BlockingZenWaveManifestLoader`. It exposes the
414
+ same I/O operations without coroutine continuations and accepts either `String` or `java.net.URI`
415
+ for root resources:
416
+
417
+ ```java
418
+ var loader = new BlockingZenWaveManifestLoader();
419
+ var manifest = loader.load(URI.create("file:///workspace/zenwave-architecture.yml"));
420
+ var service = manifest.findService("commerce/orders");
421
+ var artifact = service.findArtifact("openapi");
422
+ var options = new ManifestLoadOptions()
423
+ .withPreferredSource("workspace")
424
+ .withFallback(true);
425
+ var text = loader.loadArtifactText(manifest, service, artifact, options);
426
+ ```
427
+
428
+ For batch service documents, `loadServiceDocResults` preserves one result per configured
429
+ document, including its resolved resource or error message. `loadAvailableServiceDocs` is the
430
+ convenience variant for generators that only need successfully loaded content.
431
+ `loadServiceDocs` remains the fail-fast variant.
432
+
433
+ Candidate and reference construction remains non-blocking on the common loader. Use
434
+ `ManifestResolvedResource.referenceUri()` when a consumer needs a single URI-like reference,
435
+ including a resource stored inside an archive.
436
+
437
+ ## Browsers and Web Workers
438
+
439
+ `architecture-manifest` and `architecture-graph` load and run in a browser or Web Worker with no Node runtime.
440
+ Supply the means of reading content: every manifest, `$ref`, document and artifact read goes
441
+ through the `DocumentLoader`s you pass, and nothing else is needed to load the library.
442
+
443
+ ```kotlin
444
+ val loader = ZenWaveManifestLoader(documentLoaders = listOf(myHostReader), archiveEntryLoader = null)
445
+ val manifest = loader.load("https://example.com/zenwave-architecture.yml")
446
+ val graph = ArchitectureGraphBuilder(loader).build(manifest).graph
447
+ ```
448
+
449
+ Ask what the platform offers before presenting a feature; asking never fails:
450
+
451
+ - `ManifestPlatform.capabilities()` / `isAvailable(ManifestPlatform.ARCHIVE_EXTRACTION)`: reading an
452
+ entry inside an archive (Maven Central/GitHub Packages JARs). Available on the JVM; not on JS unless
453
+ the host passes its own `ManifestArchiveEntryLoader`.
454
+ - `ManifestPlatform.name`: `"jvm"`, `"node"` or `"browser"`.
455
+ - `RefParserPlatform` (from `json-schema-ref-parser-kmp`): filesystem, classpath and HTTP reads,
456
+ reported separately by that library and with its own `CapabilityUnavailableException`.
457
+
458
+ A candidate that needs an unavailable manifest capability fails with
459
+ `ManifestCapabilityUnavailableException`, and the next content-resolution candidate is tried; if none
460
+ succeeds, the `ManifestResourceLoadException` names the capability. The build runs the JS test suites in headless Chromium (`jsBrowserTest`, part of
461
+ `check`; set `CHROME_BIN` if no Chrome, Chromium or Edge is found in a standard location).
462
+
463
+ ## Kotlin module and verification
464
+
465
+ `architecture-manifest` 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.
466
+
467
+ Published library coordinates are `io.zenwave360.architecture:architecture-manifest`, `architecture-manifest-jvm`, and `architecture-manifest-js`.
468
+
469
+ Run:
470
+
471
+ ```text
472
+ ./gradlew :architecture-manifest:jvmTest
473
+ ```
474
+
475
+ The suite validates both schemas and every complete README YAML block marked `schema-test: valid`. Release workflow details are in [RELEASING.md](RELEASING.md); this repository is licensed under the [MIT License](LICENSE).