@ybotok/loam 0.2.0-alpha.4 → 0.2.0-alpha.6
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 +947 -0
- package/MIGRATING-from-OpenSpec.md +7 -1
- package/QUICKSTART.md +8 -3
- package/README.md +20 -13
- package/ROADMAP.md +329 -22
- package/SCHEMA.md +472 -24
- package/WORKFLOW.md +23 -10
- package/dist/commands/adopt/render.js +47 -8
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +45 -1
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/run.js +5 -2
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/context/print.js +11 -1
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/doctor.js +46 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explore.js +12 -2
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/list/views.js +5 -1
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/seed/plan.js +97 -3
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +7 -2
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +2 -1
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/marks.js +3 -5
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +39 -18
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/subsystem/sync.js +184 -39
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +124 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +29 -3
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +113 -16
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +16 -9
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +30 -77
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/vocabulary.js +4 -19
- package/dist/commands/validate/checks/vocabulary.js.map +1 -1
- package/dist/commands/validate/feature.js +1 -1
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +11 -10
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +128 -114
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +103 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +96 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +298 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +56 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +49 -17
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +13 -9
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +97 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +2 -2
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +110 -34
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +39 -5
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +41 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +10 -24
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/core/agent/agents-md/artifacts.js +1 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +18 -2
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/cycle.js +24 -9
- package/dist/core/agent/agents-md/cycle.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +51 -2
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +45 -3
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md.js +14 -4
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/support/report.js +39 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +125 -6
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +35 -19
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +3 -1
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/brief/brief.js +63 -11
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +212 -129
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +81 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +284 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +30 -26
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +12 -2
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/likec4.js +23 -1
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/parsed/deployment.js +5 -3
- package/dist/core/c4/parsed/deployment.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +142 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +52 -4
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +9 -4
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +77 -38
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +46 -12
- package/dist/core/c4/project/staged.js.map +1 -1
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/service.js +8 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +18 -0
- package/dist/core/c4/resolve/steps.js.map +1 -1
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +126 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-scan.js +26 -0
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +60 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +17 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +154 -100
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +140 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +14 -4
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +2 -1
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/coherence/coherence.js +7 -0
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/delta/delta.js +9 -0
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +4 -0
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-state.js +21 -5
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/victims.js +10 -1
- package/dist/core/diff/victims.js.map +1 -1
- package/dist/core/docs.js +31 -22
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +48 -9
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/explain/families.js +1 -1
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +1 -1
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explore/explore.js +44 -9
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +47 -0
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/partners.js +5 -9
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/pack/living.js +47 -13
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +9 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/repo/paths.js +40 -0
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +2 -3
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +51 -3
- package/dist/core/repo/tree/render/views.js.map +1 -1
- package/dist/core/repo/tree/walk.js +45 -2
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/scaffold/landscape.js +219 -9
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +1 -0
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/overlay.js +4 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +18 -6
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +7 -0
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +161 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +10 -0
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +23 -0
- package/examples/docs/architecture/landscape.likec4 +41 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/model.likec4 +53 -67
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +32 -33
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +29 -33
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +39 -59
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,953 @@ case for a change — the alternative that was rejected, the defect it came from
|
|
|
10
10
|
generalises — lives where it is maintained: [SCHEMA.md](SCHEMA.md) for the rule,
|
|
11
11
|
[ROADMAP.md](ROADMAP.md) for the priority and its exit criteria, and the commit that landed it._
|
|
12
12
|
|
|
13
|
+
## [0.2.0-alpha.6] - 2026-09-04
|
|
14
|
+
|
|
15
|
+
A service model may extend the fleet map, and the nine problem reports one fleet filed against the
|
|
16
|
+
previous release are closed. Still a prerelease on the `alpha` line: `npm i @ybotok/loam@alpha`
|
|
17
|
+
installs it. Nothing below removes a code, a flag or a payload key; two things a script may have
|
|
18
|
+
read do move: every LikeC4 line number loam prints is 1-based now, and seven feature-delta codes
|
|
19
|
+
plus the feature arm of `usecase.flow-invalid` answer `--feature <FEAT-id>` in
|
|
20
|
+
`loam explain --json` where they answered `--service <id>`. What a repo NOTICES on its first
|
|
21
|
+
`validate --all` after upgrading: a model written in the extending shape earns the renderer-wiring
|
|
22
|
+
warnings until one `loam subsystem sync` — which now also deletes a stray per-service
|
|
23
|
+
`likec4.config.json` beside such a model — a root `exclude` that hides the map is
|
|
24
|
+
`landscape.excluded`, and a standalone model whose copies of the map's elements disagree with it
|
|
25
|
+
is `c4.declaration-diverged`; a repository whose models all stand alone is asked to migrate
|
|
26
|
+
nothing, and its root `exclude` is not touched.
|
|
27
|
+
|
|
28
|
+
### Fixed — one rule for a service's own edge, and the document that actually failed, on every surface
|
|
29
|
+
|
|
30
|
+
- `loam context` and `loam explore` name the `architecture/` document that ACTUALLY failed in their
|
|
31
|
+
printed warning, off the `landscape.broken` key their `--json` payloads already carried. `parses`
|
|
32
|
+
answers for the PROJECT, so with a broken sibling the landscape file has no errors at all — and
|
|
33
|
+
both printers still said `architecture/landscape.likec4 does not parse`, a claim about bytes loam
|
|
34
|
+
had read fine. `explore`'s absent arm keeps the file name: `present` is about the FILE, which is
|
|
35
|
+
the write target every brief asks for edits to.
|
|
36
|
+
- An edge whose two endpoints resolve to the SAME service is that service's own internal wiring,
|
|
37
|
+
and no surface files it as an inbound call any more. `loam adopt --json` already dropped it; the
|
|
38
|
+
one shared derivation does now, so `loam context`, `loam explore`, `loam show` and
|
|
39
|
+
`loam vouch --pack` stopped reporting a service as its own caller (`← <id> "reads"`), and
|
|
40
|
+
`loam list`'s fleet-wide spelling of the same rule stopped reading an `op`-carrying self-edge as
|
|
41
|
+
proof that somebody calls the service — a service nobody calls was told it owed an OpenAPI
|
|
42
|
+
contract, grading `partial` in `list` while `context` said `documented`. `landscape.touched` and
|
|
43
|
+
`landscape.service-isolated` are unchanged: they read the relationships, so the map still draws
|
|
44
|
+
an edge on the service.
|
|
45
|
+
- The adopt brief's `service.model-excluded` row describes both entry shapes: one covering the
|
|
46
|
+
DIRECTORY, which `loam subsystem sync` rewrites, and one shaped like a FILE
|
|
47
|
+
(`services/**/model.likec4`), which sync cannot repair and has to be removed or narrowed by hand.
|
|
48
|
+
The finding's own message already said which one you have; the brief promised sync for both.
|
|
49
|
+
- `loam diff` classifies a base ref's tree with the same reserved-directory rule the live walk uses
|
|
50
|
+
(`adrs/`, `usecases/`, `ui/` — shared as one set, not respelled). A service directory holding only
|
|
51
|
+
the `services/<id>/usecases/` slot used to be absent from the base AND to contribute a phantom
|
|
52
|
+
service called `usecases`, so committing it and diffing the unchanged tree reported one addition
|
|
53
|
+
and one removal.
|
|
54
|
+
|
|
55
|
+
### Added — one model, many documents: a service model may extend the fleet map
|
|
56
|
+
|
|
57
|
+
- `services/<…>/<svc>/model.likec4` now comes in two shapes, and the FILE's own grammar decides
|
|
58
|
+
which — never a config key, never a flag. A model declaring no `element` kind inside a top-level
|
|
59
|
+
`specification { }` block is an **extending model**: loam parses it inside the `architecture/`
|
|
60
|
+
project and it says what is inside the service with `extend <fully-qualified id> { … }` on the
|
|
61
|
+
element the map already binds to the directory, re-declaring no kind, no tag and no partner. A
|
|
62
|
+
model that declares one is a **standalone model**, parsed alone exactly as every model was
|
|
63
|
+
before — legal forever, and not what `loam adopt` briefs any more. SCHEMA.md's new
|
|
64
|
+
"Two shapes of a service model" carries both, the grades below, and the ordered migration recipe.
|
|
65
|
+
- New `c4.declaration-diverged` (warn, service target, subject the service, one finding per element):
|
|
66
|
+
a STANDALONE model and the fleet map (`architecture/`) both declare one element, and the
|
|
67
|
+
two copies disagree about `kind`, `title`, the tag SET or the `metadata { service }` binding. The
|
|
68
|
+
message prints both declarations, names which fields differ, and offers the two repairs — copy the
|
|
69
|
+
map's declaration verbatim, or migrate to the extending shape. `description` is deliberately not
|
|
70
|
+
compared: it is prose nothing grades, while the other four each change what a view shows or what
|
|
71
|
+
loam resolves. Silent while the map does not parse. This is the check that had no code: a fleet's
|
|
72
|
+
copies of `kafka` and its identity provider had already lost `#platform` on every copy but the
|
|
73
|
+
map's, and nothing in loam read two documents together.
|
|
74
|
+
- New `c4.element-unowned` (warn, service target, subject the service, one per element): an
|
|
75
|
+
EXTENDING model declares an element outside the element that resolves to its own service. A system
|
|
76
|
+
the service reaches belongs in `architecture/landscape.likec4`, declared once; another service's
|
|
77
|
+
internals belong in that service's model.
|
|
78
|
+
- New `c4.fleet-project-invalid` (warn, `landscape` target, subject the service whose directory
|
|
79
|
+
holds the file): `--all` only, only when at least one extending model exists, the map itself
|
|
80
|
+
parses, and no root `exclude` entry hides the map (`landscape.excluded` below is then the whole
|
|
81
|
+
answer). loam loads every `.likec4` the root LikeC4 project reads except the generated
|
|
82
|
+
`architecture/subsystems.likec4` — the map, its siblings, every extending model and every
|
|
83
|
+
`.likec4` beside one — and reports each error that is NOT already present in the map alone, in a
|
|
84
|
+
per-service project, or in a service-flow project the same run made. That difference is the class
|
|
85
|
+
no per-service grade can see: two services each declaring `tag req-AUTH` locally, two models
|
|
86
|
+
adding the same child id under one element, a broken untagged sibling nobody opted in. All three
|
|
87
|
+
are renderer-fatal and all three were silent.
|
|
88
|
+
- Three new renderer-wiring warnings on the `landscape` target, root-gated like
|
|
89
|
+
`service.likec4-config-missing` and repaired by the same `loam subsystem sync`:
|
|
90
|
+
`service.likec4-config-stray` (a `likec4.config.json` beside an EXTENDING model — the renderer
|
|
91
|
+
registers a project holding nothing, or holding the model alone where it cannot parse),
|
|
92
|
+
`service.model-excluded` (the root `likec4.config.json`'s `exclude` covers an extending model's
|
|
93
|
+
directory, naming the entry that does it, so the renderer never loads the model) and
|
|
94
|
+
`service.model-unexcluded` (it does NOT cover a STANDALONE model's directory, so the renderer
|
|
95
|
+
merges that model into the map and reports every kind and element it declares as a duplicate —
|
|
96
|
+
blanking the whole root project, not one service).
|
|
97
|
+
- New `landscape.excluded` (warn, `landscape` target, `loam validate --all`) — the root
|
|
98
|
+
`likec4.config.json`'s `exclude` covers `architecture/landscape.likec4` itself. loam keeps
|
|
99
|
+
grading the map whatever that list says, so every finding in the run is right; the renderer does
|
|
100
|
+
not, and until now nothing said so. A repo notices it as one line at the top of `validate --all`
|
|
101
|
+
naming the entry verbatim — typically an `architecture/*.likec4` written for a palette — INSTEAD OF
|
|
102
|
+
the `c4.fleet-project-invalid` cascade the same entry used to produce, and it is the only line in
|
|
103
|
+
the run that names the cause. `c4.fleet-project-invalid` is not graded while the entry stands: that
|
|
104
|
+
check loads the fleet project the way the renderer does, with no floor under the map, so the one
|
|
105
|
+
line came back as an unresolved `extend` in every extending model — 161 warnings on the packaged
|
|
106
|
+
five-service example. Its message says the fleet project is unread rather than clean, and grading
|
|
107
|
+
resumes the moment the entry goes. Never gating: with the map kept by the loader, every fact loam grades holds
|
|
108
|
+
identically either way. The repair is the team's own edit — `loam subsystem sync` maintains only
|
|
109
|
+
the `services/` entries and will not touch it, and the message says so rather than sending its
|
|
110
|
+
reader to a command that reports success and changes nothing.
|
|
111
|
+
- `loam subsystem sync` now maintains the root `likec4.config.json`'s `exclude` as well as the
|
|
112
|
+
per-service files. It rewrites the list to hold `services/<tree>/**` for exactly the models that
|
|
113
|
+
stand alone: the wholesale `services/**` and every `services/<tree>/**` naming a directory that IS
|
|
114
|
+
a service are removed, every other entry is kept in the order the team wrote it (a
|
|
115
|
+
`services/legacy/**` for a directory that is no service survives), and the standalone entries are
|
|
116
|
+
appended sorted. It runs when at least one extending model exists or when a standalone model's
|
|
117
|
+
directory is not covered, so a repository whose models all stand alone behind `services/**` is
|
|
118
|
+
never touched. Same staged transaction as the rest of `sync`; `--json` gains the additive
|
|
119
|
+
`projects.exclude` (whether it rewrote, the resulting entries, and what was added and removed) and
|
|
120
|
+
the text view prints one line when it did. A root config that is not JSON is left alone with a
|
|
121
|
+
printed note.
|
|
122
|
+
- `loam init --docs <path> --create` now scaffolds `likec4.config.json` with
|
|
123
|
+
`exclude: ["**/node_modules/**", "features/**"]`. `services/` is deliberately not excluded — the
|
|
124
|
+
models a fresh repository writes extend the map, and the root project is the only place they
|
|
125
|
+
parse. A feature's `delta.likec4` keeps its own `specification` block and stays excluded.
|
|
126
|
+
- The landscape the scaffold writes teaches the extending shape. Its `views` paragraph told a fresh
|
|
127
|
+
repository that a service model "renders from its OWN directory" and that "each declares its own
|
|
128
|
+
`specification` block, so the renderer can only be given one of them at a time" — written before
|
|
129
|
+
an extending model belonged to the root project, and printed in the same `loam init --docs .
|
|
130
|
+
--create` run that now excludes `features/**` precisely because it does. And the check that says
|
|
131
|
+
a map is still untouched scaffolding now accepts EVERY generation loam has shipped, not just the
|
|
132
|
+
current text: it is a byte-suffix match, so rewording the comment reclassified every older
|
|
133
|
+
untouched scaffold as hand-authored — `loam seed` refused one its own scaffold had written
|
|
134
|
+
(`seed-landscape-edited`, nothing written) and `loam status` dropped the teaching rung from the
|
|
135
|
+
empty-fleet ladder. The two `v0.1.0-beta` stubs (no `views` block, no `tag platform`) and the
|
|
136
|
+
`v0.2.0-alpha.1`-through-`.5` stub are all recognised, frozen beside the writer.
|
|
137
|
+
- `loam adopt --service <id>` briefs the extending shape: all four `shape` rules of the
|
|
138
|
+
`model.likec4` target are rewritten, and the `example` is a `model { extend <id> { … } }` document
|
|
139
|
+
with the fleet element's real id substituted in where the brief resolves exactly one. The brief's
|
|
140
|
+
`checks[]` gains `c4.declaration-diverged`, `c4.element-unowned`, `service.likec4-config-stray`,
|
|
141
|
+
`service.model-excluded`, `service.model-unexcluded` and `c4.fleet-project-invalid`, and its
|
|
142
|
+
renderer statement in `unchecked[]` says the root project now holds the map and every extending
|
|
143
|
+
model. `/loam-adopt` steps 3, 4 and 5 and `/loam-check`'s three fix tables carry the same.
|
|
144
|
+
- The packaged example fleet (`examples/docs`, what `loam init --example` copies out) is migrated:
|
|
145
|
+
all five models extend the map, the five `services/**/likec4.config.json` are gone, the root
|
|
146
|
+
`exclude` is the scaffold's two entries, `architecture/landscape.likec4` gains `element database`
|
|
147
|
+
and the `notification-provider` system that notification-service's model used to declare for
|
|
148
|
+
itself, and every `Covers:` entry and `views` block names the map's fully-qualified ids. Its
|
|
149
|
+
`loam validate --all` result is unchanged: 0 errors and the same ten deliberate warnings.
|
|
150
|
+
- `loam archive` now writes a service's OWN `model.likec4`, not just the fleet map. A delta's
|
|
151
|
+
additions nested under a service whose model EXTENDS the map — a container, a store, a
|
|
152
|
+
container-level edge — are spliced into that model's `extend <fqn> { … }` block instead of into
|
|
153
|
+
`architecture/landscape.likec4`, and a model with no `extend` block for that fqn gains one. A
|
|
154
|
+
relationship goes to the model of the service owning its SOURCE, else its TARGET, else stays on
|
|
155
|
+
the map: a service-level edge is where every fleet check reads it. A service-level element, a
|
|
156
|
+
STANDALONE model and a service with no model.likec4 keep the previous behaviour byte for byte.
|
|
157
|
+
What a repo notices: `loam archive` prints a second
|
|
158
|
+
`architecture: merged into services/<…>/model.likec4 — +N element(s), +M relationship(s)` line
|
|
159
|
+
beside the landscape one, the model path appears in the `--json` plan (and in `--dry-run`) as an
|
|
160
|
+
ordinary write, `loam unarchive` restores it from the same `.loam-before` snapshot as any other
|
|
161
|
+
overwritten file, and the service is no longer `c4.invalid` (duplicate declaration) the moment its
|
|
162
|
+
model declares the container the adopt brief told it to declare. No new payload key and no new
|
|
163
|
+
error code. Re-archiving adds nothing a second time — existence is checked against the models too
|
|
164
|
+
— and two archives touching one model's `extend` block land adjacently.
|
|
165
|
+
- The archive's existence check now reads the extending models by TITLE as well as by id, so a
|
|
166
|
+
container a service already draws under a different identifier is recognised instead of being
|
|
167
|
+
spliced in beside itself: a delta adding `cache = container 'Order cache'` under a service whose
|
|
168
|
+
model already declares `orderCache = container 'Order cache'` adds nothing and leaves the model
|
|
169
|
+
byte for byte, which is what the same delta against the same title on the fleet map has always
|
|
170
|
+
done. Nothing is routed to that model, so the plan names only the landscape — the second
|
|
171
|
+
`merged into services/<…>/model.likec4` line appears when a model actually receives something.
|
|
172
|
+
The models' title join is scoped to one service — it fires only when both the model's
|
|
173
|
+
declaration and the addition resolve to the SAME explicitly bound service — because every
|
|
174
|
+
extending model in a fleet names a container `api`, and the map's global title index is a
|
|
175
|
+
pre-binding fallback, not a rule to spread.
|
|
176
|
+
- An extending model with no `model { }` block — an empty file, a `views { }`-only file, a tags-only
|
|
177
|
+
`specification { }`, all three shapes loam calls extending on purpose — gains one when a feature's
|
|
178
|
+
addition is routed into it, instead of refusing the whole archive with `merge-failed`. It is the
|
|
179
|
+
same call the merge already makes one level down, where a model that has never named a container's
|
|
180
|
+
parent gains the `extend <fqn> { }` block rather than refusing.
|
|
181
|
+
- `loam archive` proves the model it would write. The merged model is loaded as the project the
|
|
182
|
+
per-service grade reads it in — the `architecture/` documents with this archive's own merged
|
|
183
|
+
landscape in place, plus the model — and an archive whose model would not parse is refused at plan
|
|
184
|
+
time (`merge-failed`) with nothing written, naming the model path and the first three errors. Two
|
|
185
|
+
causes actually occur, and the refusal names both: a kind or tag the map's `specification` block
|
|
186
|
+
does not declare, and a reference to ANOTHER service's interior, which lives in that service's own
|
|
187
|
+
model and is in no project this one can see. A container-level edge whose two ends belong to two
|
|
188
|
+
different services is the case that changes: it used to merge, because both containers landed on
|
|
189
|
+
the map beside it, and it is now refused — there is no legal placement for it, and the answer is
|
|
190
|
+
to draw that call service to service.
|
|
191
|
+
- What a repo notices: nothing, until a model is written in the new shape or `loam subsystem sync`
|
|
192
|
+
is run. A repository whose models all stand alone keeps every finding it had, and its root
|
|
193
|
+
`exclude` is not touched. Write one extending model — or upgrade a repo whose scaffold predates
|
|
194
|
+
`services/**` being excluded — and the next `loam validate --all` reports the renderer-wiring
|
|
195
|
+
warnings above until one `sync`; that one sync rewrites the root `exclude`, writes the project
|
|
196
|
+
file a standalone model is owed and DELETES the stray one beside every extending model, so after
|
|
197
|
+
it the renderer's project picker holds one entry instead
|
|
198
|
+
of one per adopted service, and a fleet-wide duplicate that used to blank the whole picture is
|
|
199
|
+
reported as `c4.fleet-project-invalid` before anybody opens the renderer.
|
|
200
|
+
|
|
201
|
+
### Changed — a model's grades follow its shape, and `--service` reads the whole map project
|
|
202
|
+
|
|
203
|
+
- `service.likec4-config-missing` is narrowed to STANDALONE models. A model that extends the map
|
|
204
|
+
belongs to the root project, so it is owed no per-service `likec4.config.json` and never earns
|
|
205
|
+
this warning; the same directories earn `service.likec4-config-stray` instead while a file left
|
|
206
|
+
over from before the migration is still sitting there, and `loam subsystem sync` — which now also
|
|
207
|
+
stops writing the file for extending models — is the repair for both. The message is reworded to
|
|
208
|
+
say which shape it is about.
|
|
209
|
+
- `loam validate --service <id>` now reads the whole `architecture/` project, exactly as `--all`
|
|
210
|
+
does, instead of `architecture/landscape.likec4` alone. A use-case file under `architecture/` that
|
|
211
|
+
does not parse therefore reports `spine.landscape-invalid` on a single-service run too, where it
|
|
212
|
+
used to be visible only under `--all`. `c4.declaration-diverged` compares against that same
|
|
213
|
+
project.
|
|
214
|
+
- `c4.invalid` and `c4.valid` keep their codes and change scope with the shape. For an extending
|
|
215
|
+
model every error the per-service project raises is the MODEL's — including one the parser blames
|
|
216
|
+
on `architecture/landscape.likec4`, because the map parses clean on its own — and `c4.valid`'s
|
|
217
|
+
message reads `extends the fleet map — N elements · M relationships`. When the map itself does not
|
|
218
|
+
parse, an extending model cannot be read at all: neither code is emitted for it, and
|
|
219
|
+
`spine.landscape-invalid` carries one clause more saying the model extends the map and so cannot
|
|
220
|
+
be read either.
|
|
221
|
+
- `landscape.service-unmodelled` is raised on the `--service` target too, and stays an error. An
|
|
222
|
+
EXTENDING `model.likec4` says what is inside an element the fleet map binds, so a service whose
|
|
223
|
+
directory no map element resolves to has nothing to be inside and no count in the model means
|
|
224
|
+
anything: `loam validate --service <id>` reports it and exits 1 where the same tree used to come
|
|
225
|
+
back `c4.invalid` for a model read alone. `/loam-check`'s `--service` table carries a row of its
|
|
226
|
+
own for it and `loam explain landscape.service-unmodelled` answers for both scopes. A standalone
|
|
227
|
+
model is unaffected — the fleet run still owns that state.
|
|
228
|
+
- `subsystem.view-id-collision`'s census covers every extending model's authored view ids under
|
|
229
|
+
`--all`, not the landscape's alone: those documents are in the same root project now, and a view
|
|
230
|
+
id claimed twice there makes the renderer refuse the whole project.
|
|
231
|
+
- `spine.landscape-invalid` names the document that actually broke. The finding is raised against
|
|
232
|
+
the whole `architecture/` PROJECT and the message hardcoded `architecture/landscape.likec4`, so an
|
|
233
|
+
`architecture/usecases/x.likec4` with a typo sent every service's reader to a file that parses and
|
|
234
|
+
the real path appeared nowhere in the payload. The message now lists the broken documents
|
|
235
|
+
repo-relative and comma-separated — the spelling `landscape.invalid` already used under `--all` —
|
|
236
|
+
each `details` line carries its own file once two or more are broken, and `locations` carries the
|
|
237
|
+
first broken file as `primary` beside the service directory as `scope`. `loam explain`'s "fix the
|
|
238
|
+
file the message names" is true now.
|
|
239
|
+
- `c4.declaration-diverged` finds the copies it was written for. It joined on the literal element
|
|
240
|
+
id, so in a GROUPED map — one nesting its services under a parent, as loam's own example does — a
|
|
241
|
+
standalone model's `paymentService` never met the map's `marketplace.paymentService` and the check
|
|
242
|
+
was silent on the shape it exists for. The join is now the id; then the `metadata { service }`
|
|
243
|
+
binding when both documents wrote one; then the map element whose id ends with the model's as a
|
|
244
|
+
dotted tail. A tail two map elements answer to joins nothing — a divergence reported against the
|
|
245
|
+
wrong peer is worse than the silence. The message names the other side as `the fleet map
|
|
246
|
+
(architecture/)` rather than a file it never read, and names the map's own id when it differs.
|
|
247
|
+
- `c4.declaration-diverged` no longer joins a standalone model's `db`, `api` or `agent` to a
|
|
248
|
+
container the map draws INSIDE another service. The dotted-tail rung stands, and a tail that
|
|
249
|
+
carries a dot (`paymentService.api` ↔ `marketplace.paymentService.api`) is joined as before; the
|
|
250
|
+
BARE last segment is now a key only for a FLEET-LEVEL map element — one no ancestor of which
|
|
251
|
+
stands for a service, which is a top-level element and equally a box the map draws inside a plain
|
|
252
|
+
grouping element, by the same fleet-census predicate every other fleet check asks. Grouping is a
|
|
253
|
+
drawing convenience, not ownership: a `cache` the map draws inside an `infra` group is the FLEET's
|
|
254
|
+
cache, so a standalone model that declares its own top-level `cache` gives one name two meanings
|
|
255
|
+
and is reported. Only a SERVICE owns an interior. What a repo notices: a private cache called
|
|
256
|
+
`db` beside another service's `db` was warned about and told to copy that service's title
|
|
257
|
+
verbatim; it is now silent. loam's own `meta/docs` carried the workaround for it, and no longer
|
|
258
|
+
does — its `coding agent` element is spelled `agent` again.
|
|
259
|
+
- `c4.declaration-diverged` stops asking for a copy that is already there — in both directions.
|
|
260
|
+
Since LikeC4 1.59.0 a kind can carry tags and every element of that kind arrives with them, so a
|
|
261
|
+
tag difference can be one no element declaration writes and no copy can fix. loam now decides that
|
|
262
|
+
by SIMULATING the copy rather than by matching one shape of it: when the tag sets are all that
|
|
263
|
+
differ and copying the map's literal tags across would leave them still differing, the message
|
|
264
|
+
says so, names `specification { element <kind> { … } }` as where they come apart, and says WHICH
|
|
265
|
+
document declares tags on that kind, so an author knows which file to open. Both shapes reach it
|
|
266
|
+
now: the model writing `#external` on an element whose map twin inherits `#external #platform`
|
|
267
|
+
from `element topic { … }` used to be told to copy the map's bare line, and doing exactly that
|
|
268
|
+
produced the other arm of the same message (measured on `examples/docs`' order-service). Grammar:
|
|
269
|
+
"the kind differs" / "the title differs" / "the tags differ".
|
|
270
|
+
- Three messages say what they could not say before. `c4.invalid` on a model written in BOTH shapes
|
|
271
|
+
at once — an element kind declared in `specification { }` beside an `extend` — gains one `details`
|
|
272
|
+
line saying the kind is what makes the file standalone, so the `extend` resolves nothing, and
|
|
273
|
+
which of the two lines to drop. `usecase.flow-invalid` whose project refuses an undeclared
|
|
274
|
+
`#req-`/`#cap-` tag gains one line naming the `tag <name>` declaration that is missing and the
|
|
275
|
+
`specification { }` block it belongs in. And `c4.valid` gains ` · K tagged flow(s) graded` when
|
|
276
|
+
the service's own project declared any, so a HEALTHY intra-service flow is visible on one surface
|
|
277
|
+
instead of none. Both hints are the FIRST `details` line rather than the last: the cap is ten
|
|
278
|
+
lines in text and in `--json`, and a model written in both shapes at once produces dozens of
|
|
279
|
+
cascading errors, so the one line that diagnosed it was truncated away in exactly the case it
|
|
280
|
+
exists for.
|
|
281
|
+
- A `.likec4` beside `model.likec4` that the service's own `likec4.config.json` `exclude` covers is
|
|
282
|
+
no longer read, graded or reported. loam staged every sibling regardless, so a file the renderer
|
|
283
|
+
never loads earned `usecase.flow-invalid` under a message claiming the project is read the way the
|
|
284
|
+
renderer reads it. Standalone models only — that file IS the project config there; a model that
|
|
285
|
+
extends the map is read in the root project, and a per-service config beside it is
|
|
286
|
+
`service.likec4-config-stray`. The RECURSIVE spelling counts too: a leading `**/` compiled to a
|
|
287
|
+
required `/`, so `**/usecases/**`, `**/views.likec4` and `**/*.likec4` matched nothing while the
|
|
288
|
+
renderer (measured, likec4 1.59.2) hides exactly what they name — `usecase.flow-invalid` gated the
|
|
289
|
+
run at exit 1 over a file in no project loam was reading.
|
|
290
|
+
- `spine.landscape-invalid` says "a, b have N error(s)" when it names more than one document; it
|
|
291
|
+
read "a, b has N error(s)".
|
|
292
|
+
- `c4.element-unowned` leads with the remedy that fits a private store. The message listed the two
|
|
293
|
+
remedies that move an element OUT of the service — the map, or another service's model — and
|
|
294
|
+
omitted the one an author usually wants: a store or component this service owns goes INSIDE the
|
|
295
|
+
`extend <fqn> { }` block, where its id becomes `<fqn>.<name>`. Following what the tool printed
|
|
296
|
+
moved a private store to fleet level and earned `landscape.service-undocumented`.
|
|
297
|
+
|
|
298
|
+
### Added — problem reports carry an ordinal and a status, and `loam doctor` counts them
|
|
299
|
+
|
|
300
|
+
- The `loam-report` protocol (`loam instructions loam-report`) names its file
|
|
301
|
+
`loam-reports/NNN-YYYY-MM-DD-<short-slug>.md`: `NNN` is an ordinal of at least three digits,
|
|
302
|
+
zero-padded, one
|
|
303
|
+
more than the highest already in the directory, `001` over an empty or absent one, widening past
|
|
304
|
+
three digits rather than reusing a number, and the
|
|
305
|
+
protocol tells the agent to read it from `loam doctor --json` rather than count the directory
|
|
306
|
+
itself. A path that already exists is a concurrent report claiming that ordinal — take the next
|
|
307
|
+
one; the collision suffix is gone. That check is on the PATH while the promise is about the
|
|
308
|
+
ordinal, so the protocol also tells the agent to re-run `doctor` after writing: two entries
|
|
309
|
+
sharing an ordinal means the later `Recorded` renames to the next free one.
|
|
310
|
+
The directory is `loam-reports/` beside the `loam.json` that resolved
|
|
311
|
+
(else beside the directory the agent runs in) — `reports.dir` carries it WITH that segment on the
|
|
312
|
+
end, so it is written into rather than joined onto — the ordinal is unique within it, and a
|
|
313
|
+
report is cited by
|
|
314
|
+
ordinal — in a commit message, an issue or another report's cross-references — never by filename.
|
|
315
|
+
The document template gains `- Status: open` directly after `- Classification:`, with the
|
|
316
|
+
vocabulary `open` / `sent` (handed to the author — the person forwarding a report sets it, never
|
|
317
|
+
the agent writing it) / `fixed in <version>` / `superseded by <NNN>`; a report is closed by
|
|
318
|
+
editing its own `Status` line, and there is no index to keep in step with it. Every safety
|
|
319
|
+
sentence is unchanged: `<redacted>`, "Never re-run" a writer, "do not upload", and the section
|
|
320
|
+
headings.
|
|
321
|
+
- `loam doctor --json` gains one additive key, `reports`: `reports.dir` (the absolute path of the
|
|
322
|
+
`loam-reports` directory beside the `loam.json` that resolved — and, when a `loam.json` was FOUND
|
|
323
|
+
but could not be parsed, beside that file, which is the same root `agents` is inspected against;
|
|
324
|
+
only where no config file exists at or above the run directory does it fall back to the run
|
|
325
|
+
directory. The last segment is ON it, so the protocol writes inside the path rather than
|
|
326
|
+
joining the name a second time), `reports.present`, `reports.total`, `reports.next` (the ordinal
|
|
327
|
+
the next report takes, padded
|
|
328
|
+
to at least three digits and widening past them, `001` when there is none), and `entries[]` —
|
|
329
|
+
every `*.md` in the directory
|
|
330
|
+
sorted by the ordinal its name claims, numerically, with unnumbered legacy names last (byte order
|
|
331
|
+
stopped being report order at exactly the width the ordinal widens to: `1000-…` sorts before
|
|
332
|
+
`998-…`), each carrying `file`, `ordinal` (the leading three-or-more-digit ordinal,
|
|
333
|
+
`null` for a name written before the numbering, including the `YYYY-MM-DD-…` shape) and
|
|
334
|
+
`status` (`open` | `sent` | `fixed` | `superseded` | `unstated`, read as the first word of the
|
|
335
|
+
`- Status:` line in the report's HEADER FIELD BLOCK — the lines above the first `##` heading,
|
|
336
|
+
quoted code skipped in BOTH markdown spellings, fenced and indented, so a report quoting the
|
|
337
|
+
protocol's own template counts as whatever its own header says — and `fixed in 0.2.0-alpha.5`
|
|
338
|
+
counts as `fixed`). The
|
|
339
|
+
human view prints one row after `agents`:
|
|
340
|
+
`reports loam-reports/ 12 · open 3 · sent 5 · fixed 3 · superseded 1 · next 013` — zero
|
|
341
|
+
counts omitted, the total always shown, `reports (none)` when the directory is absent or
|
|
342
|
+
empty.
|
|
343
|
+
- Reading it is best-effort and emits no finding: an unreadable file is an entry with status
|
|
344
|
+
`unstated`, an unreadable directory is `present: true` with `total: 0`, and a status word this
|
|
345
|
+
vocabulary does not know is `unstated` rather than a sixth bucket. No new code is emitted and
|
|
346
|
+
`doctor`'s `healthy` does not move for anything in the directory — an open report is not a
|
|
347
|
+
defect in the repository holding it. Reading stays local: nothing is transmitted, which is the
|
|
348
|
+
constraint the protocol is built around.
|
|
349
|
+
- AGENTS.md's support paragraph now says reports are numbered and that `loam doctor` counts the
|
|
350
|
+
directory and says which ordinal is next.
|
|
351
|
+
- What a repo notices: `loam doctor` prints a `reports` row it never printed before — `(none)`
|
|
352
|
+
on a repository that has never written one — and `loam doctor --json` carries a `reports` object
|
|
353
|
+
beside `agents`. Existing reports keep their names and are counted as `unstated` with no
|
|
354
|
+
ordinal until someone renames them and adds a `Status:` line; the next report an agent writes
|
|
355
|
+
lands as `loam-reports/001-…` in a directory of legacy names, because `next` is one past the
|
|
356
|
+
highest ordinal present and a `YYYY-MM-DD-…` name has none.
|
|
357
|
+
|
|
358
|
+
### Added — the adopt brief on an edgeless landscape element
|
|
359
|
+
|
|
360
|
+
- `loam adopt --service <id> --json` gains two additive keys under `landscape`: `landscape.touched`
|
|
361
|
+
— `true` when at least one edge in the `architecture/` project has an endpoint resolving to
|
|
362
|
+
the service (an intra-service edge counts: the predicate `loam context` prints as "(modelled, no
|
|
363
|
+
edges touch it)"), `null` whenever `landscape.modelled` is `null` or `false` — and
|
|
364
|
+
`landscape.attested`, the calls the service's own `model.likec4` declares across its boundary
|
|
365
|
+
(`direction`, `counterpartId`, `counterpart`, and `title` / `op` / `publishes` / `consumes` where
|
|
366
|
+
the edge carries them), sorted and de-duplicated, `[]` when the model is absent, does not parse,
|
|
367
|
+
or declares none, and read only in the edgeless state. `landscape.modelled` and
|
|
368
|
+
`landscape.elements` are now decided by the same element→service resolver `validate --all` grades
|
|
369
|
+
`landscape.service-unmodelled` on: a container titled like a sibling service, nested inside an
|
|
370
|
+
element bound to another service, resolves to its ancestor — so `loam adopt` reports such a
|
|
371
|
+
service `modelled: false` where it reported `true`, which is the answer the fleet run was already
|
|
372
|
+
giving.
|
|
373
|
+
`landscape.instruction` is no longer `null` on element existence alone: while an element resolves
|
|
374
|
+
to the service and no edge touches it, the brief names each attested call and asks for it as ONE
|
|
375
|
+
edge on the existing element, collapsed to the service and naming the other party as the map
|
|
376
|
+
already spells it — or, with nothing attested, points at the walk's outbound and message stops and
|
|
377
|
+
says draw NOTHING rather than invent an edge — and names the element's tags loam does not read (a
|
|
378
|
+
placeholder such as `#provisional`, and the view written `exclude element.tag = #<that>` that
|
|
379
|
+
keeps an adopted service hidden). The `architecture/landscape.likec4` target is back in that state
|
|
380
|
+
with `action: "edit"`, `required: true`, a shape refusing a second element and an edge-only
|
|
381
|
+
example. The text view prints the instruction under "what the fleet already says about this
|
|
382
|
+
service" and flags the row `EDGELESS` ("an element exists, no edge does") instead of `UNDRAWN`.
|
|
383
|
+
Until now `loam seed --from fleet.yaml` left every service absent from `calls:` as a bound,
|
|
384
|
+
edgeless element the brief then declared finished.
|
|
385
|
+
- New `landscape.service-isolated` (warn, never gating, `--all` only, on the landscape target,
|
|
386
|
+
`subject` the service id): an element resolves to `services/<…>/<svc>/`, no edge anywhere in the
|
|
387
|
+
`architecture/` project touches it, and that service's `model.likec4` parses and declares at least
|
|
388
|
+
one call across its
|
|
389
|
+
boundary — the message names the first three. Evidence-gated on purpose: silent for a service with
|
|
390
|
+
no model (a seeded fleet), an unparseable model (`c4.invalid` owns it), a model reaching nothing,
|
|
391
|
+
an unmodelled service (`landscape.service-unmodelled` owns it), and a service the map already
|
|
392
|
+
touches with ONE edge while its model attests five — the check is touched/untouched, not a set
|
|
393
|
+
difference; `#external` elements are never
|
|
394
|
+
subjects. It suppresses `landscape.matched` like every finding. The brief's `checks[]`, the
|
|
395
|
+
`/loam-check` `--all` table and AGENTS.md's adopt step carry it; `/loam-adopt` step 5 states
|
|
396
|
+
the silent cases and points at `landscape.touched` in step 1.
|
|
397
|
+
- One more statement in the adoption brief's unchecked list, now seventeen: whether an adopted
|
|
398
|
+
service is VISIBLE on the fleet map — a placeholder tag that survives adoption, a curated view
|
|
399
|
+
that lists its members by id, and `include *` collapsing to a boundary element
|
|
400
|
+
(`include <boundary>.**`). `/loam-adopt` step 3 names that housekeeping as part of the landscape
|
|
401
|
+
edit.
|
|
402
|
+
- `loam adopt --service <id>` reads the fleet map as the `architecture/` PROJECT — the landscape
|
|
403
|
+
plus every other `.likec4` under `architecture/`, minus the generated `subsystems.likec4` — where
|
|
404
|
+
it read `architecture/landscape.likec4` alone. It is the document set `landscape.service-isolated`
|
|
405
|
+
already grades `touched` over, so `landscape.modelled`, `landscape.elements`, `landscape.inbound`,
|
|
406
|
+
`landscape.outbound`, `landscape.expects`, `landscape.touched`, `landscape.attested` and
|
|
407
|
+
`landscape.instruction` now answer about the same map `loam validate --all` does. What a repo
|
|
408
|
+
notices: a fleet that keeps a second `model { }` block, a use case or a palette in a sibling
|
|
409
|
+
`architecture/*.likec4` no longer gets a brief demanding edges it has already drawn —
|
|
410
|
+
`touched: true`, `instruction: null`, and no eighth `architecture/landscape.likec4` target — where
|
|
411
|
+
the fleet run on the same tree reported `landscape.matched`. The same widening makes
|
|
412
|
+
`landscape.parses` false, and `landscape.modelled` `null`, when any document under `architecture/`
|
|
413
|
+
fails to parse: the landscape file parsing on its own does not rescue a map the renderer cannot
|
|
414
|
+
load. No key added, no key removed. The edgeless instruction names the element's home as "the
|
|
415
|
+
fleet map (architecture/)" rather than a file it did not read — the WRITE still lands in
|
|
416
|
+
`architecture/landscape.likec4`, and the target's `path` says so.
|
|
417
|
+
- The adopt brief's `model.likec4` example is headed with the service being adopted
|
|
418
|
+
(`// order-service's own C4 …`) instead of the literal `payment-service` it carried for every
|
|
419
|
+
service — one line above an `extend <fqn>` the brief already substituted, so the personalised half
|
|
420
|
+
taught an agent the rest of the header had been personalised too. `<svc>` joins `<fqn>` as a
|
|
421
|
+
placeholder the shape rules leave literal and `serviceBrief` fills in; the example never leaves
|
|
422
|
+
either in the payload.
|
|
423
|
+
- What a repo notices: a drawn-but-edgeless service whose model reaches other systems gains one
|
|
424
|
+
`landscape.service-isolated` warning per service on its next `loam validate --all` — under
|
|
425
|
+
`--strict`, CI is red until an edge lands or the element is genuinely edgeless and stays so (a
|
|
426
|
+
model attesting no cross-boundary call is silent); a seeded service nobody listed under `calls:`
|
|
427
|
+
is briefed the edges instead of nothing.
|
|
428
|
+
|
|
429
|
+
### Added — a home for an intra-service use case
|
|
430
|
+
|
|
431
|
+
- A `dynamic view` over one service's own containers now has a slot loam reads and grades: any
|
|
432
|
+
`.likec4` beside `services/<…>/<svc>/model.likec4` (`usecases/<name>.likec4` by convention,
|
|
433
|
+
`views.likec4` read the same) or a tagged view inside model.likec4 itself — exactly the document
|
|
434
|
+
set the per-service renderer project registers, read whether or not `likec4.config.json` exists.
|
|
435
|
+
`loam validate --service <id>`, and `--all` per service, grade every view tagged
|
|
436
|
+
`#req-<Requirement-ID>` with the codes the fleet target already uses: `usecase.step-unbacked`
|
|
437
|
+
(error), `usecase.step-contested` (warn), `usecase.requirement-unresolved` (error — resolved
|
|
438
|
+
against THIS service's spec.md and arch.spec.md ids, four arms), `usecase.capability-unresolved`
|
|
439
|
+
(error — a `#cap-` tag beside a model is refused by placement, vocabulary or not) and
|
|
440
|
+
`usecase.flow-invalid` (error — a sibling that breaks the service project; model.likec4 keeps
|
|
441
|
+
grading alone, never `c4.invalid`). Untagged views are never graded, and an untagged sibling is
|
|
442
|
+
not even loaded while no view in the project opts in and no sibling mentions either reserved
|
|
443
|
+
prefix anywhere in its bytes; once loaded — a tagged view inside model.likec4 itself, or `req-` /
|
|
444
|
+
`cap-` in a sibling's comment or title — a project that does not read is one
|
|
445
|
+
`usecase.flow-invalid` whether or not any view in it opted in. A resolved service-local `#req-`
|
|
446
|
+
feeds nothing else yet. The feature slot for such a flow is deferred (ROADMAP).
|
|
447
|
+
- Every surface that names a code for a `#req-` tag declared TWICE — once in the map's
|
|
448
|
+
`specification` and once in a tags-only block beside the `extend` — now names the one loam
|
|
449
|
+
actually emits. SCHEMA.md, `/loam-adopt` step 3 and `loam instructions loam-codes` all said
|
|
450
|
+
`c4.fleet-project-invalid` (a warning); measured, the finding lands where the second declaration's
|
|
451
|
+
FILE is: `c4.invalid` — an error that gates, suspends the model's grading and cascades six
|
|
452
|
+
`covers.unknown` — when the tags-only block is inside model.likec4, and `usecase.flow-invalid`
|
|
453
|
+
when it is in a sibling. `c4.fleet-project-invalid` is the fleet-scope class one step out: two
|
|
454
|
+
SERVICES each declaring the same tag locally, which no per-service project can see. No behaviour
|
|
455
|
+
changed; an agent routing on the printed code was being sent to the wrong severity and the wrong
|
|
456
|
+
repair.
|
|
457
|
+
- Two corrections to the shared step graders, at fleet altitude too: a hop whose two endpoints
|
|
458
|
+
resolve to ONE service is no longer backed by that service's internal edges on the service tier
|
|
459
|
+
(measured: every internal relationship was a candidate, so an unbacked hop between two containers
|
|
460
|
+
of one service graded attributed and could never earn `usecase.step-unbacked`); and
|
|
461
|
+
`usecase.step-unlinked` no longer fires when the caller resolves to the provider — a service owes
|
|
462
|
+
no operationId to itself, and that is the guard's whole scope: a hop from a service-local flow
|
|
463
|
+
into another service's element still warns, exactly as it does on the fleet map. What a repo
|
|
464
|
+
notices: a landscape that draws a service as containers with a tagged hop between two of them
|
|
465
|
+
loses a wrong `usecase.step-unlinked` warning and may gain a right `usecase.step-unbacked` error;
|
|
466
|
+
a service directory holding a tagged `dynamic view` beside its model starts being graded.
|
|
467
|
+
- `loam diff` no longer counts a service as a consumer of its own operation, on either half of the
|
|
468
|
+
question. A landscape edge whose SOURCE and TARGET both resolve to the provider is the provider's
|
|
469
|
+
own internal call — the rule `usecase.step-unlinked` already states — and so is a use-case hop
|
|
470
|
+
whose two endpoints resolve to it; the edge half excluded nothing while the message half already
|
|
471
|
+
did, so the downgrade this list promised could not be observed in the configuration it described.
|
|
472
|
+
Both are excluded now, and it is: a removal whose only trace in the fleet is the provider calling
|
|
473
|
+
itself reports `diff.op-removed` (warn) and the run exits 0, where it reported
|
|
474
|
+
`diff.op-removed-consumed` (error, `breaking: true`, exit 1). A second service's edge naming the
|
|
475
|
+
operation is still `diff.op-removed-consumed`.
|
|
476
|
+
- The adopt brief's `checks[]` gains six `--service` rows: the five above and
|
|
477
|
+
`usecase.step-unlinked`, which the service target reports for a hop into a sibling's stand-in, and
|
|
478
|
+
which `/loam-check`'s `--service` table now carries a row of its own for. The arch.spec.md shape
|
|
479
|
+
names the hop-sequence slot; AGENTS.md's layout paragraph names the slot itself
|
|
480
|
+
(`services/<…>/<svc>/usecases/*.likec4`) and the styling opt-in beside it.
|
|
481
|
+
- `loam explain` answers the right scope for the eight codes only a feature run raises. A markdown
|
|
482
|
+
fix table ends at the first line that is not a `|` row, and nothing separated `/loam-check`'s
|
|
483
|
+
`--feature <FEAT-id>` rows from the `--service <id>` ones sitting above them, so
|
|
484
|
+
`capability.uncovered`, `capability.remove-requirement-realized`, `glossary.term-exists`,
|
|
485
|
+
`usecase.flow-exists`, `deployment.doc-exists`, `deployment.doc-invalid`,
|
|
486
|
+
`deployment.doc-reserved` and the feature/archive arm of `usecase.flow-invalid` all came back
|
|
487
|
+
`scope: "--service <id>"` — a service run can raise none of them. They now read
|
|
488
|
+
`--feature <FEAT-id>`. What a consumer notices: `entries[].scope` changes value for those eight
|
|
489
|
+
in `loam explain <code> --json` and in `loam explain --codes --json`; the meanings and the fixes
|
|
490
|
+
are unchanged, and no code was added or removed.
|
|
491
|
+
|
|
492
|
+
### Changed — the generated subsystem views join the repository's styling
|
|
493
|
+
|
|
494
|
+
Every view in the generated `architecture/subsystems.likec4` now carries `global style subsystems`
|
|
495
|
+
— written after its `title` and `description`, before its `include`/`group` — exactly when the
|
|
496
|
+
`architecture/` project (the landscape merged with every other `.likec4` under `architecture/` the
|
|
497
|
+
root `exclude` does not cover — a `palette.likec4` beside the map counts, and one under an excluded
|
|
498
|
+
path is loaded by nobody; the one
|
|
499
|
+
load `loam subsystem sync`, `validate --all` and `archive` share) declares a global style with the
|
|
500
|
+
id `subsystems`, in either form: `global { styleGroup subsystems { … } }` or
|
|
501
|
+
`global { style subsystems … }`. loam reads only that the id is declared — a census of the keys of
|
|
502
|
+
`$data.globals.styles` beside the view-id census in `core/c4/parsed/view-ids.ts` — and never what
|
|
503
|
+
the group says (docs/DESIGN.md rule 26). Nothing is written otherwise, and the file is
|
|
504
|
+
byte-identical to before. When the project declares style groups under other names only, one
|
|
505
|
+
comment line between the header and `views {` names them and says none is `subsystems`; there is
|
|
506
|
+
no single-group fallback, because a second group declared for a hand-drawn view would otherwise
|
|
507
|
+
switch the generated views' styling off on a change that touched no subsystem. Styling therefore
|
|
508
|
+
restales the generated file on two triggers, and `subsystem.views-stale`, `loam explain` and the
|
|
509
|
+
fix tables name both: declaring or removing the group named `subsystems` changes whether the views
|
|
510
|
+
carry the `global style subsystems` line, and declaring or removing a group under ANY OTHER name
|
|
511
|
+
changes the disclosure comment. What a repo notices:
|
|
512
|
+
a fleet that already declares a group named `subsystems` (or any group, for the disclosure line)
|
|
513
|
+
sees one `subsystem.views-stale` on its next `validate --all`, cleared by one `loam subsystem sync`.
|
|
514
|
+
`loam seed` renders the file from that same project — the landscape it is about to write, staged
|
|
515
|
+
beside every other `architecture/` document — so a palette kept in a sibling such as
|
|
516
|
+
`architecture/usecases/palette.likec4` is referenced by the seeded views and never a stale file
|
|
517
|
+
straight after a seed. No new code, flag or `--json` key. `LoadedDoc` gains the additive field
|
|
518
|
+
`globalStyles` (the sorted declared ids), produced alike by every loader and pinned by the three
|
|
519
|
+
parity suites; seven tripwire assertions in `test/likec4-view-shape.test.ts` pin the parsed shape
|
|
520
|
+
at likec4@1.59.2.
|
|
521
|
+
|
|
522
|
+
### Changed — the fleet's consumer census reads the models, not the map alone
|
|
523
|
+
|
|
524
|
+
- `landscape.datastore-private`, `landscape.datastore-shared` and `landscape.platform-candidate`
|
|
525
|
+
count a consumer that a service's own EXTENDING `model.likec4` attests — an outbound call at the
|
|
526
|
+
element or a descendant — beside the edges `architecture/` draws. Under the extending shape the
|
|
527
|
+
container-level call lives in the model and only the service-level twin reaches the map, so a
|
|
528
|
+
store whose consuming edges were all drawn in models had zero consumers and earned nothing at all,
|
|
529
|
+
and a store reached by three models plus one map edge was reported `landscape.datastore-private`
|
|
530
|
+
naming the single service the map happened to draw. Extending models only: their ids are the map's
|
|
531
|
+
fully-qualified ids, so the join is exact, while a standalone model's ids are its own file's and
|
|
532
|
+
are never matched to a landscape element. A model that does not parse, or cannot be read,
|
|
533
|
+
contributes nothing.
|
|
534
|
+
- `landscape.datastore-shared` now fires for a NESTED store. A store sitting inside the element that
|
|
535
|
+
resolves to its owner — declared there on the map, or added by the owner's extending model — is
|
|
536
|
+
private by construction and silent at one consumer, and is graded at two: `subject` is the store's
|
|
537
|
+
title as before, and the message adds ` — <owner>'s own store, written as '<fqn>'` beside the
|
|
538
|
+
consuming services it names — the store's OWN id, since `<fqn>` IS the store and the earlier
|
|
539
|
+
"nested under" sent a reader looking for a containing element of that name. The message then
|
|
540
|
+
states the reversibility directionally: the two placements give the store the same id, but with a
|
|
541
|
+
second consumer the move is one-way — out of the `extend` block onto the map is always safe, and
|
|
542
|
+
back into it breaks every other consumer's edge.
|
|
543
|
+
Until now the
|
|
544
|
+
strongest coupling two services can have rendered perfectly and was graded nowhere, because the
|
|
545
|
+
census only ever looked at elements drawn at fleet level.
|
|
546
|
+
- `landscape.datastore-private`'s remedy names the placement instead of a directory, and names it
|
|
547
|
+
for the consumer's own model SHAPE rather than assuming one. For an EXTENDING consumer it says to
|
|
548
|
+
write the store in that model's `extend <fqn> { … }` block, or inside `<fqn>` on the map when the
|
|
549
|
+
map draws that service's containers. For a consumer whose model STANDS ALONE — or that loam could
|
|
550
|
+
not read as extending — it says the map placement and offers the migration to the extending shape
|
|
551
|
+
as the other arm, because `extend <fqn>` in a standalone model resolves nothing (such a model is
|
|
552
|
+
parsed alone) and following the old sentence there turned a run with 0 errors into `c4.invalid`.
|
|
553
|
+
Either way it states what happens next: a second service
|
|
554
|
+
reaching the same data draws a container-level edge to `<fqn>.<name>` in its own model plus a
|
|
555
|
+
service-level edge on the map, and loam counts that consumer — so the state is
|
|
556
|
+
`landscape.datastore-shared`, never silence. It also states which of the two placements keeps that
|
|
557
|
+
door open: the edge resolves only against a store the MAP declares, because a per-service project
|
|
558
|
+
is `architecture/` plus one model. The old "or add the second consumer's edge" is gone as an equal
|
|
559
|
+
remedy and survives as "if another service reaches the same data today, draw its edge and this
|
|
560
|
+
becomes that warning now". It also states the measured reversibility with its condition — the two
|
|
561
|
+
placements give the store the same id, `<fqn>.<store>`, and with one consumer either move is free:
|
|
562
|
+
out of the `extend` block onto the map always is, and back into it while no other service's model
|
|
563
|
+
or map edge names the store (measured on `examples/docs`: moving `db` out of order-service's `extend`
|
|
564
|
+
block into `orderService` on the map left the model at 12 elements · 8 relationships, left
|
|
565
|
+
`Covers: marketplace.orderService.db` in its `arch.spec.md` resolving, and left checkout-web's
|
|
566
|
+
container-level edge to it resolving) — and it qualifies the "never silence" promise: a consumer
|
|
567
|
+
whose model stands alone is not counted, because its ids are its own file's, so its edge belongs
|
|
568
|
+
on the map.
|
|
569
|
+
- `landscape.datastore-private` and `landscape.datastore-shared` never take an element that stands
|
|
570
|
+
for a `services/<id>/` as their subject, at ANY depth. The fleet-level half has always skipped
|
|
571
|
+
one (a datastore bound to a directory is the binding checks' subject); the NESTED half did not,
|
|
572
|
+
so a `database` carrying `metadata { service '<id>' }` and drawn inside another service's
|
|
573
|
+
element became a store "owned" by its own binding, and one more reader made it
|
|
574
|
+
`landscape.datastore-shared` — advising the team to give each service a private copy of a
|
|
575
|
+
service. Both halves and the model-declared store census now share one `standsForService`
|
|
576
|
+
predicate, handed down on the input record because a `fleet/map/` module may not import `fleet/`.
|
|
577
|
+
- `landscape.invalid`'s message generalises its hint. It spoke about use-case flows alone, so an
|
|
578
|
+
author whose MODEL EDGE named a container was told to move a flow that does not exist. It now says
|
|
579
|
+
that an unresolved name an extending model declares — a use-case hop or an edge naming
|
|
580
|
+
`<service-fqn>.<container>` — renders for the renderer and resolves for nobody here, and that the
|
|
581
|
+
repair is to name the SERVICE on the map and the container in that service's own model.
|
|
582
|
+
- `landscape.service-isolated` never takes an `#external` element as its subject. The claim rested
|
|
583
|
+
on "an `#external` element is not an enumerated service", which is false for an element carrying
|
|
584
|
+
the tag AND a `metadata { service '<id>' }` binding to a directory that exists: the input set had
|
|
585
|
+
no tag filter, so a foreign box answered for one of our services. The isolation input SET now
|
|
586
|
+
skips `#external`, matching `landscape.binding-duplicate` and
|
|
587
|
+
`landscape.binding-unknown`. `landscape.service-unmodelled` keeps the unfiltered set — the
|
|
588
|
+
directory IS drawn. The `touched` walk applies NO tag filter, deliberately: it answers a different
|
|
589
|
+
question — does the map draw any edge on this service — and LikeC4 puts a kind tag on every
|
|
590
|
+
element of the kind, so `element topic { #external }` (the idiom
|
|
591
|
+
`examples/docs/architecture/landscape.likec4` documents) made an edge drawn from a service's OWN
|
|
592
|
+
topic stop touching that service. The warning fired on a service the map demonstrably reaches
|
|
593
|
+
while `loam adopt` reported `touched: true` on the same tree; the walk is the brief's
|
|
594
|
+
`landscape.touched` predicate exactly, tag and all.
|
|
595
|
+
|
|
596
|
+
### Changed — the renderer wiring, measured rather than assumed
|
|
597
|
+
|
|
598
|
+
- `loam subsystem sync` now DELETES a `likec4.config.json` that sits beside a model which EXTENDS
|
|
599
|
+
the fleet map, in the same journaled commit as everything else it writes, and `--json` `projects`
|
|
600
|
+
gains `projects.removed` (POSIX paths relative to the docs root, sorted by path (not by service
|
|
601
|
+
id — the survey's own order); `[]` on a run that removed none)
|
|
602
|
+
beside `projects.created`. The text view prints one `removed <path>` line per file and one
|
|
603
|
+
sentence saying why. The note this corrects said such a nested project was registered EMPTY and
|
|
604
|
+
therefore harmless; measured on likec4 1.59.2 it CLAIMS the model — the workspace loads two
|
|
605
|
+
projects, the model is Invalid inside its own one (the map's kinds are not in there with it) and
|
|
606
|
+
`export json --project fleet` loses the service's containers wherever that nested project claims
|
|
607
|
+
the model (measured both ways at the 1.59.2 pin: it did on a seeded fleet, and did not on
|
|
608
|
+
`examples/docs`, where the root project kept the model — what happens in every state is that the
|
|
609
|
+
docs root stops being a one-project workspace and `likec4 validate .` refuses without
|
|
610
|
+
`--project`). A repo that migrated 56 models to
|
|
611
|
+
`extend` and ran one `sync` kept 56 stray files and 57 projects; it now keeps one. A leftover
|
|
612
|
+
beside a model that is GONE still stays — that one really is the team's, and a nested project
|
|
613
|
+
holding no `.likec4` is silently absent from the renderer's list. `service.likec4-config-stray`'s
|
|
614
|
+
message is rewritten to the measured consequence and now names a repair `sync` actually performs.
|
|
615
|
+
`projects.current` narrows with it: it counts the services owed a project file that already had
|
|
616
|
+
one, and a model that extends the map is owed none — so on the run that deletes the strays it
|
|
617
|
+
drops by exactly the length of `projects.removed`, and every run after that reports the same
|
|
618
|
+
number both spellings would have.
|
|
619
|
+
- `loam subsystem sync` no longer rewrites the generated views file when the map cannot be read.
|
|
620
|
+
`action` gains the value `blocked`: with `architecture/` unreadable and
|
|
621
|
+
`architecture/subsystems.likec4` already present, the run writes nothing about that file, prints
|
|
622
|
+
the reason, and `--json` carries that action plus `blocked.reason` reading `landscape-invalid` —
|
|
623
|
+
exit 0, because `loam validate --all`'s `landscape.invalid` is the gating surface. Before this,
|
|
624
|
+
any load error rendered the tolerant shape: the file was rewritten down to title and description,
|
|
625
|
+
losing the `global style subsystems` line, every `include` and the boundary comment, while `sync`
|
|
626
|
+
reported `updated … 1 subsystem view(s)` and answered `current` ever after. The render also now
|
|
627
|
+
falls back to the landscape FILE when only a SIBLING document breaks the project, so a duplicate
|
|
628
|
+
`subsystems` group declared in two `architecture/` documents costs nothing. With no generated file
|
|
629
|
+
yet, and inside a tree-changing verb's commit window (`new`/`move`/`rm`/`rename`, whose contract is
|
|
630
|
+
that the file lands with the change), the tolerant shape is still written and the same one-line
|
|
631
|
+
note is printed.
|
|
632
|
+
- The `architecture/` document set every reader shares now honours the root `likec4.config.json`'s
|
|
633
|
+
`exclude`. A `.likec4` under `architecture/` the renderer never loads is no longer loaded by loam
|
|
634
|
+
either: a palette under an excluded path used to yield a `global style subsystems` reference the
|
|
635
|
+
fleet project could not resolve — `likec4 validate` Invalid on every run while `loam validate
|
|
636
|
+
--all` reported 0 errors, `doctor` was healthy and `sync` said "current" — and a broken excluded
|
|
637
|
+
document blanked the whole map. A root config loam cannot read an `exclude` list out of filters
|
|
638
|
+
nothing.
|
|
639
|
+
- An `exclude` entry is now read the way the renderer reads it, measured rather than reasoned about.
|
|
640
|
+
loam interpreted only a literal `/**` suffix, so a root config written `services/**/*`,
|
|
641
|
+
`services/*`, `services/` or the bare `services/<tree>` hid every extending model with zero
|
|
642
|
+
findings; the first repair of that read the entry as a walk over path SEGMENTS, which is not what
|
|
643
|
+
LikeC4 does either. Measured at the 1.59.2 pin on a scratch fleet of five documents, by putting
|
|
644
|
+
one entry in the root config and counting the "N files" `npx likec4 validate .` reports: a bare
|
|
645
|
+
entry is a raw PREFIX with no end anchor (`services/pay` -> 3 files, hiding BOTH
|
|
646
|
+
`services/payment-service` and `services/payments/payment-service`; `architect` -> 3, hiding all of
|
|
647
|
+
`architecture/`), it must start on a `/` boundary (`ervices` and `ces/payment-service` -> 5, that
|
|
648
|
+
is, nothing), `*` stays inside one segment while `**` crosses (`architecture/*.likec4` -> 4, so
|
|
649
|
+
the nested use case survives; `*.likec4` -> 0), and a glob form does NOT prefix-match
|
|
650
|
+
(`services/pay/**` -> 5). LikeC4 prefixes an entry that does not begin with a doubled star with
|
|
651
|
+
one, joins it onto the project folder and matches with picomatch `contains: true` — the missing
|
|
652
|
+
end anchor is that option. So `service.model-excluded` fires now, naming the entry verbatim, on
|
|
653
|
+
every spelling including a starred middle segment; `service.model-unexcluded` no longer
|
|
654
|
+
false-positives on a standalone model whose directory such an entry already covers; and
|
|
655
|
+
`subsystem sync` takes back an entry that hides an extending model whatever its spelling — the
|
|
656
|
+
write side asked "which directory does this entry name", which answers nothing for a starred
|
|
657
|
+
segment, so `services/*/svc-b` hid a model forever while `service.model-excluded` named that same
|
|
658
|
+
command as the repair — and appends nothing for a directory the surviving list already covers.
|
|
659
|
+
- The root `exclude` filter on the `architecture/` document set now has a floor:
|
|
660
|
+
`architecture/landscape.likec4` stays in the set whatever the list says. Without one, an entry a
|
|
661
|
+
team writes for a palette (`architecture/*.likec4` covers both) left the architecture project
|
|
662
|
+
holding ZERO documents — and an empty project parses with zero errors, so every reader took it for
|
|
663
|
+
"the map declares nothing". On a seeded two-service fleet that produced four errors, all false:
|
|
664
|
+
`landscape.service-unmodelled` twice, naming the very file that models both services and telling
|
|
665
|
+
the reader to add an element already in it, plus `c4.invalid` on the extending model; and
|
|
666
|
+
`loam subsystem sync` answered `updated` and rewrote a good generated file down to a title, with
|
|
667
|
+
no line in either run mentioning the `exclude` entry that caused it. loam now grades one document
|
|
668
|
+
the renderer may skip rather than asserting a fleet's whole architecture says nothing.
|
|
669
|
+
- `loam subsystem sync` answers `blocked` whenever the `architecture/` PROJECT does not parse, not
|
|
670
|
+
only when `architecture/landscape.likec4` itself is unreadable. The landscape-file fallback that
|
|
671
|
+
saves the element join says nothing about the STYLE CENSUS — a palette may live in any `.likec4`
|
|
672
|
+
under `architecture/` — so a run made while an unrelated sibling was broken reported `updated`,
|
|
673
|
+
deleted the `global style subsystems` line from a good generated file, answered `current` ever
|
|
674
|
+
after, and never earned `subsystem.views-stale` while the map stayed invalid. The file is now left
|
|
675
|
+
byte-identical in that state and the note names what was unavailable.
|
|
676
|
+
- `loam subsystem sync --json` carries `projects.exclude.unreadable` (boolean, emitted on every
|
|
677
|
+
run). `entries: []` alone collapsed the two answers loam insists must stay apart: "the root
|
|
678
|
+
project excludes nothing" and "loam could not read an `exclude` list out of the root file". The
|
|
679
|
+
text view has always had a note for the second; a machine consumer reading `entries: []` got the
|
|
680
|
+
opposite of the truth about what the renderer will load. Additive, beside `projects.exclude.removed`.
|
|
681
|
+
- `loam subsystem sync`'s "— nothing to write." tail is dropped when the run rewrote the root
|
|
682
|
+
`likec4.config.json`'s `exclude`. It counted only the per-service project files created and
|
|
683
|
+
removed, so a run with a current views file printed "nothing to write." immediately above the line
|
|
684
|
+
reporting the rewrite.
|
|
685
|
+
- `c4.fleet-project-invalid` now enumerates what the renderer actually loads. The document set is a
|
|
686
|
+
walk of the docs root — everything the root `exclude` leaves, minus the generated
|
|
687
|
+
`architecture/subsystems.likec4`, minus every standalone service directory (owned by
|
|
688
|
+
`service.model-unexcluded`), minus `features/` (a delta carries its own `specification` and the
|
|
689
|
+
directory is transient). A `.likec4` in a SUBSYSTEM directory — the parent of service directories
|
|
690
|
+
— was in none of the three roots the check used to merge: `validate --all` came back
|
|
691
|
+
byte-identical to baseline and never named the file while `npx likec4 validate --project fleet .`
|
|
692
|
+
reported Invalid over the same tree. The finding also gains `locations` (the file the error is IN,
|
|
693
|
+
not `architecture/landscape.likec4` on every one of them) and `details`, and its tail now names
|
|
694
|
+
the failure it actually is — a double declaration, an unresolved reference, or neither — instead
|
|
695
|
+
of always blaming a double declaration.
|
|
696
|
+
- Every LikeC4 line number loam prints is 1-based, the way an editor counts — and deliberately NOT
|
|
697
|
+
the way `likec4 validate` prints it, which passes the LSP range's start line through and so says
|
|
698
|
+
`Line 8` for a fault on line 9.
|
|
699
|
+
`c4.invalid`'s details said `L3` for an error on line 4; `c4.fleet-project-invalid` printed
|
|
700
|
+
`model.likec4:0` for a declaration on line 1; and the feature arm of `usecase.flow-invalid`
|
|
701
|
+
printed no line at all and an ABSOLUTE Windows path into loam's own staging tree — it now spells
|
|
702
|
+
the authored file docs-relative with forward slashes and an `L<n>`, exactly as the service arm of
|
|
703
|
+
the same code does, in the text view, in `--json` and in the `loam archive` block message.
|
|
704
|
+
|
|
705
|
+
### Fixed — the adoption brief answers off the document it actually read
|
|
706
|
+
|
|
707
|
+
- `loam adopt` no longer briefs a write to the shared fleet map when the map could not be READ.
|
|
708
|
+
The eighth target (`architecture/landscape.likec4`) is emitted on `landscape.modelled === false`
|
|
709
|
+
— a read that succeeded and found nothing — and on the edgeless state; `modelled: null` ("loam
|
|
710
|
+
could not read the map", which since the `architecture/`-project widening includes a broken
|
|
711
|
+
palette or use case beside a landscape that parses perfectly) now emits no target at all.
|
|
712
|
+
It used to fall through the same gate as "the map draws nothing for this service", so the brief
|
|
713
|
+
handed an agent the UNDRAWN block — "add one top-level element bound to its directory" — for a
|
|
714
|
+
service the map already declares and binds, and splicing it earned `landscape.binding-duplicate`.
|
|
715
|
+
`landscape.instruction` carries the whole answer instead and names the documents that failed.
|
|
716
|
+
- `loam adopt`'s fleet-map instruction and the text view's landscape headline no longer name
|
|
717
|
+
`architecture/landscape.likec4` as the broken file when the document that failed is a SIBLING
|
|
718
|
+
under `architecture/`. The instruction now names the failing documents docs-relative with their
|
|
719
|
+
error count — the same spelling `validate --all`'s `landscape.invalid` prints — and the headline
|
|
720
|
+
says `The fleet map (architecture/) does not parse — fix that first; the files are named below.`
|
|
721
|
+
The brief and the fleet run on the same tree used to blame two different files.
|
|
722
|
+
- `loam adopt` no longer marks `openapi.yaml` required because a document beside the fleet map
|
|
723
|
+
broke. The API question is answered off the landscape FILE (`landscape.callersKnown`, added to
|
|
724
|
+
`--json`: true when the `architecture/` project parses, and true when it does not but the
|
|
725
|
+
landscape file does), which is the evidence `loam list` and `loam context` already read: a broken
|
|
726
|
+
`architecture/palette.likec4` used to flip `openapi.yaml` from `create missing` to
|
|
727
|
+
`create MISSING` for a browser UI the map proves nobody calls, and told the agent to write an API
|
|
728
|
+
contract for it. With the landscape file itself unreadable the contract is still owed.
|
|
729
|
+
- `loam adopt`'s model.likec4 example prints a short `<your-element-id>` where loam has no id to
|
|
730
|
+
substitute, and carries the reason once as a `//` comment above the block. The placeholder used
|
|
731
|
+
to be a 103-character English sentence spliced into all three code positions `<fqn>` occupies —
|
|
732
|
+
the `extend` line, the source of a container edge and `view of` — on the command's most common
|
|
733
|
+
invocation, a brand-new adoption.
|
|
734
|
+
- `loam adopt`'s arch.spec.md shape rule named `c4.fleet-project-invalid` (warn, `--all`) for a
|
|
735
|
+
`#req-` tag declared both in the map and beside the model. Measured, that is `c4.invalid` (error,
|
|
736
|
+
and the model's grading is suspended behind it) when the tags-only block is in model.likec4 and
|
|
737
|
+
`usecase.flow-invalid` when it is in a sibling; `c4.fleet-project-invalid` is the fleet-scope
|
|
738
|
+
class one step out, two SERVICES each declaring the same tag locally. The rule now says so, and
|
|
739
|
+
matches the same statement in SCHEMA.md.
|
|
740
|
+
- `loam adopt`'s model.likec4 shape rule for `c4.element-unowned` leads with the nested remedy —
|
|
741
|
+
a store or component this service OWNS goes INSIDE the `extend <fqn> { }` block, where its id
|
|
742
|
+
becomes `<fqn>.<name>` — before the two existing arms, matching the `--service` fix table and
|
|
743
|
+
SCHEMA's grades table.
|
|
744
|
+
- `loam adopt`'s `landscape.invalid` check description said "architecture/landscape.likec4 exists
|
|
745
|
+
but does not parse". It now says the `architecture/` PROJECT does not parse — the landscape or
|
|
746
|
+
any `.likec4` beside it — and that the message names the document that broke.
|
|
747
|
+
|
|
748
|
+
### Fixed — one tree, one answer: the gate's preview, and the map's other readers
|
|
749
|
+
|
|
750
|
+
- The merge preview every feature-side gate reads is now routed exactly as `loam archive` routes:
|
|
751
|
+
the fleet's extending models reach it, so an addition the delta nests inside a service is
|
|
752
|
+
previewed in that service's `model.likec4` and not on the map. Without them the preview held
|
|
753
|
+
interior the archive puts somewhere else, and `usecase.flow-invalid` — the gate `--approve`
|
|
754
|
+
cannot override — failed open on the one shape it exists for. Measured on a copy of
|
|
755
|
+
`examples/docs`: a feature nesting `cache` under `marketplace.orderService` and hopping
|
|
756
|
+
`checkoutWeb -> marketplace.orderService.cache` from `features/<FEAT>/usecases/` passed
|
|
757
|
+
`validate --feature`, archived at exit 0, and left a fleet whose next `validate --all` reported 7
|
|
758
|
+
errors — one `landscape.invalid` plus one `spine.landscape-invalid` per service — against a file
|
|
759
|
+
its author never wrote. The gate now refuses before anything is written, `validate --feature`
|
|
760
|
+
gives the same verdict, and a flow hopping between elements the map itself draws still archives.
|
|
761
|
+
`deployment.doc-invalid` is wired the same way and had the same hole.
|
|
762
|
+
- `loam context <service>` reads the fleet map as the `architecture/` PROJECT — the landscape
|
|
763
|
+
merged with every use case, palette and second `model { }` block beside it — never
|
|
764
|
+
`architecture/landscape.likec4` alone. Two commands answered opposite things about one tree:
|
|
765
|
+
measured on a two-service fleet with a broken `architecture/palette.likec4` beside a landscape
|
|
766
|
+
that parses, `loam adopt --service order-service` reported `parses: false` and refused to say
|
|
767
|
+
anything about the map, while `loam context order-service` reported `parses: true`,
|
|
768
|
+
`modelled: true` and listed the map's edges — and an agent handed both has no way to tell which is
|
|
769
|
+
right. `landscape.present` is still a fact about the FILE; `landscape.parses` is now a fact about
|
|
770
|
+
the project the renderer loads, so a broken sibling reads here as "nobody could look", the answer
|
|
771
|
+
`validate --all` and `adopt` already gave. The second load of that same project the deployment
|
|
772
|
+
slice used to make when the landscape file declared no `deployment { }` block is gone with it: one
|
|
773
|
+
parse, one answer.
|
|
774
|
+
- `loam seed` computes the generated views file's style census from the `architecture/` documents
|
|
775
|
+
the root `likec4.config.json`'s `exclude` leaves, not from a raw walk of the directory. A
|
|
776
|
+
`palette.likec4` under an excluded path is loaded by neither the renderer nor `validate --all`,
|
|
777
|
+
yet seed read it and wrote `global style subsystems` into `architecture/subsystems.likec4` — a
|
|
778
|
+
name the fleet project cannot resolve, written by the command whose own `next` line is
|
|
779
|
+
`loam validate --all`. The same defect one command over from the one the exclude filter fixed in
|
|
780
|
+
the loader. The fleet use-case scan's cheap tag gate was corrected with it: it walked
|
|
781
|
+
`architecture/` for itself, which could only over-scan (it is a prefilter before a load that does
|
|
782
|
+
apply the exclude), but two lists that disagree about what the project is are two answers waiting
|
|
783
|
+
to drift.
|
|
784
|
+
- `landscape.invalid`'s head reads `<documents> have N error(s)` once more than one document broke,
|
|
785
|
+
matching `spine.landscape-invalid`. The named list is comma-joined when the `architecture/`
|
|
786
|
+
project has errors in two files, and the fleet line said "a, b has 4 error(s)" directly above the
|
|
787
|
+
service line saying "a, b have 4 error(s)" about the same two files — one report, two spellings of
|
|
788
|
+
one fact.
|
|
789
|
+
|
|
790
|
+
### Fixed — a second verification round: what the archive routes, and what every message names
|
|
791
|
+
|
|
792
|
+
- `loam archive` no longer refuses a delta written in the legacy title-join spelling inside a group
|
|
793
|
+
the map declares. Routing an addition into a service's extending model now requires the delta to
|
|
794
|
+
spell the whole parent chain in the MAP's own ids — a living element, a container an extending
|
|
795
|
+
model already declares, or a declaration this same delta adds — instead of only requiring that
|
|
796
|
+
*some* ancestor be living. A grouped map (`marketplace = group { … }`, the shape
|
|
797
|
+
`loam init --example` ships) made the old guard pass on `marketplace.orderSvc`, the addition was
|
|
798
|
+
written as `extend marketplace.orderSvc` — an id the map has never declared — and the model's
|
|
799
|
+
parse net refused the whole archive with `merge-failed` and nothing written. What a repo notices:
|
|
800
|
+
such a delta merges onto `architecture/landscape.likec4` under the map's own id and exits 0 again,
|
|
801
|
+
exactly as it does when the service's model is standalone or absent. The documented remainder is
|
|
802
|
+
unchanged: a title-joined parent keeps the map path.
|
|
803
|
+
- A service a feature INTRODUCES now keeps its interior out of the fleet map when its directory
|
|
804
|
+
already holds an extending model. The new element's containers arrive inside its authored bytes and
|
|
805
|
+
carry no feature tag of their own — LikeC4 does not inherit tags, so nothing in the addition set
|
|
806
|
+
could see them — and they rode onto the map: `loam adopt --service <svc>` then told the team to
|
|
807
|
+
write the same container into `extend <fqn> { }`, and the renderer reported a duplicate element
|
|
808
|
+
blamed on both files while the service graded `c4.invalid`. Now the box lands on the map without
|
|
809
|
+
its interior, the children are spliced into the model's `extend <fqn> { }` block (created if
|
|
810
|
+
absent), and `loam archive` prints the second
|
|
811
|
+
`architecture: merged into services/<…>/model.likec4` line for it. A service arriving with no
|
|
812
|
+
directory at all, and one whose block also draws an internal edge between its own containers, keep
|
|
813
|
+
the previous behaviour byte for byte — an edge written inside the block names its endpoints by
|
|
814
|
+
their local names and there is no legal split, so the block rides whole. That is ONE decision and it covers the whole block: a child or an edge
|
|
815
|
+
the delta tags with the feature rides with a block that rides and is spliced with a block that
|
|
816
|
+
splits, never placed twice. While the two were decided separately the tagged child landed in both
|
|
817
|
+
documents and the merge refused itself, `merge-failed`, on a delta neither half was wrong about.
|
|
818
|
+
- Both C4 merges now splice with the target document's own line endings. A CRLF
|
|
819
|
+
`architecture/landscape.likec4` or `services/<…>/model.likec4` came back with a handful of bare-LF
|
|
820
|
+
lines in the spliced region, and a CRLF delta's blank lines arrived carrying the target indent as
|
|
821
|
+
trailing whitespace. A document with one convention keeps it; a document that is already mixed is
|
|
822
|
+
left alone, because there is no convention to match.
|
|
823
|
+
- `landscape.datastore-shared` and `landscape.datastore-private` now state the reversibility of a
|
|
824
|
+
private store's placement **directionally**, because the unconditional clause was false in every
|
|
825
|
+
state `datastore-shared` prints in. The two placements do give the store the same id
|
|
826
|
+
(`<owner-fqn>.<store>`), but the move is only free one way: out of the owner's `extend` block onto
|
|
827
|
+
the owner's element on the map is always safe, and back INTO the `extend` block breaks every other
|
|
828
|
+
consumer's edge — `c4.invalid` on that service's model, or `landscape.invalid` when the edge is
|
|
829
|
+
drawn on the map — because a per-service project is `architecture/` plus one model, so an `extend`
|
|
830
|
+
block is visible to its own service's project alone. `datastore-shared` needs a second consumer to
|
|
831
|
+
fire, so the state it prints in is exactly the state in which map→extend breaks; `datastore-private`
|
|
832
|
+
has one consumer, so it says both moves are free *today* and names the condition that ends that.
|
|
833
|
+
- `landscape.datastore-private`'s no-model arm offers a model to WRITE, not one to migrate: with no
|
|
834
|
+
`model.likec4` at all it now says "write an extending model at `<path>`" one sentence after saying
|
|
835
|
+
loam read no extending model there. The STANDALONE arm still says "migrate the model", which is
|
|
836
|
+
correct for it.
|
|
837
|
+
- `landscape.service-isolated`'s message names the fleet map rather than one file: "no edge in the
|
|
838
|
+
fleet map (architecture/) touches it". The check's input is the `architecture/` PROJECT — an edge
|
|
839
|
+
added to `architecture/extra.likec4` and never to `landscape.likec4` silences it, measured — so the
|
|
840
|
+
message now agrees with SCHEMA, with `loam explain landscape.service-isolated` and with the adopt
|
|
841
|
+
brief, all three of which already said "anywhere in `architecture/`".
|
|
842
|
+
- `loam doctor` prints LikeC4 line numbers 1-based, through the same `errorText` helper every other
|
|
843
|
+
surface uses. `doctor.landscape-invalid` hand-rolled its own `(line <n>)` spelling and passed the
|
|
844
|
+
LSP range through raw, so it named the line above the fault and disagreed with `validate --all`
|
|
845
|
+
about one error on one tree. The message now reads
|
|
846
|
+
`architecture/landscape.likec4 does not parse: L<n>: <message> — and N more`.
|
|
847
|
+
- `loam context --json` and `loam explore --json` gain `landscape.broken`: the `architecture/`
|
|
848
|
+
documents that failed, docs-relative POSIX, deduped and sorted, `[]` while `landscape.parses` is
|
|
849
|
+
true. Spelled exactly as `landscape.invalid` and the adopt brief spell them, so the three surfaces
|
|
850
|
+
name one file one way. Both commands read the `architecture/` PROJECT, so the document at fault is
|
|
851
|
+
very often a sibling beside a landscape that reads perfectly.
|
|
852
|
+
- The adopt brief no longer promises a warning that cannot fire. On an element carrying `#external`,
|
|
853
|
+
the edgeless instruction and the `landscape.likec4` target's shape rule say that
|
|
854
|
+
`landscape.service-isolated` will NOT fire (the check removes every `#external` element from its
|
|
855
|
+
subject set), that this brief is the only place the state is named, and that dropping `#external`
|
|
856
|
+
makes the check apply again. Every other tree keeps the previous sentence.
|
|
857
|
+
- The adopt brief's `checks[]` row for `c4.declaration-diverged` names "the fleet map
|
|
858
|
+
(`architecture/`)" instead of `architecture/landscape.likec4`. The map is a project; LikeC4 hands
|
|
859
|
+
back no source document for an element, and the finding's own message and `loam explain` already
|
|
860
|
+
refused to name a file.
|
|
861
|
+
- `loam adopt --json`'s `landscape.inbound`/`landscape.outbound` drop an edge whose two endpoints
|
|
862
|
+
resolve to the SAME service — a service's own internal wiring drawn on the map made it its own
|
|
863
|
+
caller. `landscape.touched` is unchanged: it is now read off the map's relationships rather than
|
|
864
|
+
off those two lists, so it still counts an intra-service edge and still matches
|
|
865
|
+
`landscape.service-isolated`'s predicate exactly.
|
|
866
|
+
- The service arm's use-case findings (`usecase.step-unbacked`, `usecase.step-unlinked`,
|
|
867
|
+
`usecase.requirement-unresolved`, `usecase.capability-unresolved`) carry the flow FILE as
|
|
868
|
+
`locations[0]` with role `primary` and the service directory beside it as `scope`. They carried the
|
|
869
|
+
service directory alone, so a `--json` consumer had to parse the prose to learn which `.likec4` to
|
|
870
|
+
open. `usecase.flow-invalid` on the same arm already named its file.
|
|
871
|
+
- `usecase.flow-invalid`'s undeclared-tag hint tells a `cap-` author the placement rule instead of a
|
|
872
|
+
declaration that cannot help: a capability is claimed at fleet altitude, so declaring `tag cap-…`
|
|
873
|
+
beside a model makes the file parse and earns `usecase.capability-unresolved` on the next run. The
|
|
874
|
+
`req-` hint is unchanged.
|
|
875
|
+
- The services tree walk never classifies a service's own reserved subdirectory (`adrs`, `usecases`,
|
|
876
|
+
`ui` — SCHEMA's service layout) as a service or a subsystem. Writing the documented
|
|
877
|
+
`services/<id>/usecases/<name>.likec4` slot into a directory holding no artifact FILE used to make
|
|
878
|
+
the walk read `usecases` as a service and its parent as an unmarked group: three errors
|
|
879
|
+
(`subsystem.unmarked`, `landscape.service-unmodelled` for `services/<id>/usecases/`,
|
|
880
|
+
`landscape.binding-unknown` for the real service) earned by following the instruction `loam init`
|
|
881
|
+
had just scaffolded. Such a directory is also not counted when deciding whether a directory is a
|
|
882
|
+
group, so a real service filed beside a reserved slot still refuses with `subsystem.unmarked` and
|
|
883
|
+
is still named.
|
|
884
|
+
- `loam` now strips a leading UTF-8 byte-order mark before parsing the root `likec4.config.json`.
|
|
885
|
+
PowerShell's `Out-File` writes one by default, so a Windows shell saving the file loam itself wrote
|
|
886
|
+
left every `exclude` grade silent while the renderer went on applying the list (measured:
|
|
887
|
+
`npx likec4 validate .` at 3 source files of 8 with `services/**` in force, `loam validate --all`
|
|
888
|
+
at 0 errors and `doctor` healthy). A rewrite through `loam subsystem sync` drops the mark.
|
|
889
|
+
- `service.model-excluded` asks the FILE question too. An `exclude` entry shaped like a file
|
|
890
|
+
(`services/**/model.likec4`, `**/model.likec4`) hides a model as thoroughly as one naming its
|
|
891
|
+
directory, and nothing said so: on `examples/docs` such a line took the renderer to 3 source files
|
|
892
|
+
of 8 while `loam validate --all` printed the string "exclude" not once. It is reported now, with
|
|
893
|
+
the repair that fits it — `loam subsystem sync` maintains only the `services/` directory entries,
|
|
894
|
+
so the file shape has to be narrowed or deleted by hand, and the message says which shape you have.
|
|
895
|
+
- `loam seed` no longer overwrites a hand edit written ABOVE the scaffold's landscape stub.
|
|
896
|
+
`isLandscapeStub` matched with `endsWith`, so anything a person put before the stub was invisible
|
|
897
|
+
to the guard: a two-line "OWNED BY THE ARCHITECTURE GUILD — do not regenerate" header was replaced
|
|
898
|
+
without a word while seed printed "replaced the scaffold's untouched stub", against its own refusal
|
|
899
|
+
text ("Seed never overwrites human work"). The suffix match existed for exactly one prefix — the
|
|
900
|
+
comment preamble `migrate-openspec` stages above the identical stub, closed by a bare `//` line —
|
|
901
|
+
so that is now the only prefix accepted, at every scaffold generation. What a repo notices:
|
|
902
|
+
`loam seed` refuses with `seed-landscape-edited` and writes nothing, and `loam status` calls such a
|
|
903
|
+
map `authored` rather than `stub`. A repo staged by `migrate-openspec` still seeds.
|
|
904
|
+
- `loam doctor` counts the report corpus that is there when the config is the thing that is broken.
|
|
905
|
+
Read from a SUBDIRECTORY of a repo whose `loam.json` does not parse, it used to report
|
|
906
|
+
`total 0, next 001` while naming the broken config's own path in the same envelope — handing out an
|
|
907
|
+
ordinal five existing reports already used. `doctor` is the preflight that runs precisely when the
|
|
908
|
+
config may not resolve, so `reports.dir` now resolves beside the `loam.json` it FOUND.
|
|
909
|
+
- `/loam-check`'s `c4.fleet-project-invalid` row spells the `details` format the finding emits:
|
|
910
|
+
`<path>: L<n>: <error>`, with the colon after the path that the message's own head has always
|
|
911
|
+
carried. The row promised `<path> L<n>: <error>`, so an agent splitting on the documented separator
|
|
912
|
+
split in the wrong place.
|
|
913
|
+
|
|
914
|
+
## [0.2.0-alpha.5] - 2026-09-03
|
|
915
|
+
|
|
916
|
+
An adopted service's model renders from the docs root. Still a prerelease on the `alpha` line:
|
|
917
|
+
`npm i @ybotok/loam@alpha` installs it. Nothing below is breaking. What a repo with adopted services
|
|
918
|
+
NOTICES: one `service.likec4-config-missing` warning per service on its first `validate --all`
|
|
919
|
+
until one `loam subsystem sync`, and from that sync on `--project` for `likec4 validate` at the
|
|
920
|
+
docs root.
|
|
921
|
+
|
|
922
|
+
### Added — an adopted service's model renders from the docs root
|
|
923
|
+
|
|
924
|
+
The root `likec4.config.json` excludes `services/**`, and has to; what that cost was never stated:
|
|
925
|
+
an adopted service's `model.likec4` — the artifact `loam adopt` spends the most effort on — was a
|
|
926
|
+
box on the fleet map with nothing renderable inside it in the renderer opened at the docs root, and
|
|
927
|
+
the only documented way to see its containers was one `npx likec4 start services/<id>` per service.
|
|
928
|
+
One problem report; one file per service closes it.
|
|
929
|
+
|
|
930
|
+
- `loam subsystem sync` now writes `services/<…>/<svc>/likec4.config.json` beside every service
|
|
931
|
+
`model.likec4` that has none, whenever the root `likec4.config.json` exists — **create-only**: a
|
|
932
|
+
file that exists is yours and is never compared, rewritten or removed. Measured at the pinned
|
|
933
|
+
likec4, a project file inside a service directory registers the model as a project of its own
|
|
934
|
+
regardless of the root's exclusion, so `npx likec4 start` at the docs root shows `fleet` plus one
|
|
935
|
+
project per service. Without the root file nothing is written and the text says `loam doctor`
|
|
936
|
+
prints it. `subsystem sync --json` carries an additive `projects` key, `{ root, created, current }`
|
|
937
|
+
— whether the root file exists, the repo-relative paths written this run (sorted by id), and how
|
|
938
|
+
many model-bearing services already had one. `action` still describes
|
|
939
|
+
`architecture/subsystems.likec4` alone.
|
|
940
|
+
- `loam validate --all` gains one warning per service, `service.likec4-config-missing`, on the
|
|
941
|
+
`landscape` target with the service's tree path as its `scope`: `model.likec4` exists and the
|
|
942
|
+
project file does not. Never an error, never gating, never in `--service`, `status` or `doctor`;
|
|
943
|
+
the fix is `loam subsystem sync`, never a hand-written file. `doctor.likec4-config-missing`'s fix
|
|
944
|
+
text now hands over to `sync` once the root file is written.
|
|
945
|
+
- The project name: the id with every `.` folded to `-` (LikeC4's name grammar refuses the byte),
|
|
946
|
+
prefixed `service-` when that would equal the root project's `fleet` or LikeC4's reserved
|
|
947
|
+
`default`; `title` is the id verbatim, and the title is what the renderer's picker shows. Not
|
|
948
|
+
injective — an id spelled billing.core beside one spelled billing-core, or `service-fleet` beside
|
|
949
|
+
`fleet` — and LikeC4 suffixes the later one `-1`; disclosed, never graded.
|
|
950
|
+
|
|
951
|
+
What a repo NOTICES on upgrade: one warning per adopted service on the first `validate --all` until
|
|
952
|
+
one `loam subsystem sync` — under `--strict`, CI is red until then; after that `sync`, N untracked
|
|
953
|
+
files (`sync` does not `git add` them) and `move-uncommitted` on the next `loam subsystem move` of
|
|
954
|
+
such a service until they are committed; from the first `sync` on, `likec4 validate` at the docs
|
|
955
|
+
root needs `--project <name>` (measured: with more than one project it refuses bare, while
|
|
956
|
+
`likec4 build` and `likec4 export` take every project and need no flag); and the renderer's project
|
|
957
|
+
list grows by one per adopted service, beside `fleet`. No command, flag, exit code or envelope key
|
|
958
|
+
changes; `projects` is additive.
|
|
959
|
+
|
|
13
960
|
## [0.2.0-alpha.4] - 2026-09-03
|
|
14
961
|
|
|
15
962
|
The view title becomes the subsystem's path. `0.2.0-alpha.3` was tagged and **never published** — it
|