@ybotok/loam 0.2.0-alpha.5 → 0.2.0-alpha.7
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 +1063 -0
- package/MIGRATING-from-OpenSpec.md +7 -4
- package/QUICKSTART.md +8 -3
- package/README.md +13 -7
- package/ROADMAP.md +285 -11
- package/SCHEMA.md +566 -45
- 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 +53 -23
- 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/commit.js +34 -1
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- 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/edit.js +4 -2
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +55 -17
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/sync.js +238 -87
- 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 +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +126 -22
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +159 -19
- 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/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.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 +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +131 -120
- 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 +102 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +109 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +219 -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/scorecard/scorecard.js.map +1 -1
- 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 +45 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +258 -70
- package/dist/commands/validate/fleet/views/projects.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +99 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +32 -15
- 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 +19 -5
- 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 +70 -17
- 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/refusals.js +5 -3
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +26 -7
- 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 +123 -14
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +34 -20
- 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/agent/workflows/reference/spine.js +12 -6
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/brief/brief.js +88 -19
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +133 -7
- 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 +94 -29
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +13 -3
- 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 +77 -7
- 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 +12 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +85 -42
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +53 -15
- 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 +16 -10
- 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 +29 -24
- 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 +36 -8
- 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/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/checks.js +5 -1
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +65 -26
- 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 +45 -38
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- package/dist/core/repo/tree/render/projects.js +93 -35
- package/dist/core/repo/tree/render/projects.js.map +1 -1
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +20 -4
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +149 -32
- 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/verify/checklist.js +42 -10
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +51 -1
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +30 -8
- package/examples/docs/architecture/landscape.likec4 +47 -0
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -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 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -39
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/examples/docs/services/checkout-web/likec4.config.json +0 -4
- package/examples/docs/services/order-service/likec4.config.json +0 -4
- package/examples/docs/services/payment-service/likec4.config.json +0 -4
- package/examples/docs/services/platform/identity-service/likec4.config.json +0 -4
- package/examples/docs/services/platform/notification-service/likec4.config.json +0 -4
|
@@ -10,7 +10,7 @@ export const LOAM_ADOPT = {
|
|
|
10
10
|
"wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet",
|
|
11
11
|
"`loam adopt` — the brief: the order to read the service in, every file to write, the grammar of each, and what the fleet map already says",
|
|
12
12
|
"walk the service in the brief's order — shape first, then one surface at a time — keeping the list of every path you open, because that list becomes `sources`",
|
|
13
|
-
"write the artifacts, everything `status: draft
|
|
13
|
+
"write the artifacts, everything `status: draft` — the model EXTENDS the fleet map — then `loam subsystem sync` for the renderer wiring, which is loam's and never yours",
|
|
14
14
|
"validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible",
|
|
15
15
|
"hand back with what you could not determine from the code, which directories you never opened, the branch-to-scenario count per operation, and three behaviours your documents do not describe — then let a human vouch",
|
|
16
16
|
],
|
|
@@ -43,6 +43,13 @@ thin baseline that validates is thin, not done.
|
|
|
43
43
|
read it, diff your findings against it, report what disagrees. Do not replace it.
|
|
44
44
|
\`action: "edit"\` appears on exactly one target — \`architecture/landscape.likec4\`,
|
|
45
45
|
the WHOLE FLEET's map: add this service's element and edges to it, never rewrite it.
|
|
46
|
+
It appears while nothing resolves to the service AND while the element that does
|
|
47
|
+
has no edge touching it; \`targets[].shape\` says which of the two you are in.
|
|
48
|
+
It does NOT appear when loam could not READ the map (\`landscape.modelled: null\`
|
|
49
|
+
— a broken landscape, or a broken sibling under \`architecture/\`): nothing is
|
|
50
|
+
known about what the map already draws, so no write to the whole fleet's map is
|
|
51
|
+
briefed. \`landscape.instruction\` carries the answer and names the documents that
|
|
52
|
+
failed; fix those and re-run.
|
|
46
53
|
- \`targets[].shape\` — the grammar of each artifact, and \`example\` where one is
|
|
47
54
|
shorter than a description. Every rule there is one a later check depends on;
|
|
48
55
|
rules nothing checks are in \`unchecked[]\` instead, and are still worth following.
|
|
@@ -50,7 +57,13 @@ thin baseline that validates is thin, not done.
|
|
|
50
57
|
Bind to them; do not draw a second version of the same box. \`landscape.expects\`
|
|
51
58
|
lists operations other services already call — your openapi.yaml owes them.
|
|
52
59
|
\`landscape.instruction\` is the write the fleet map still owes this service; it
|
|
53
|
-
is \`null\` once an element resolves to it
|
|
60
|
+
is \`null\` once an element resolves to it AND at least one edge ANYWHERE in the
|
|
61
|
+
\`architecture/\` project touches that element (\`landscape.touched\`), and only
|
|
62
|
+
then — one edge closes the state, so draw every call in one pass.
|
|
63
|
+
\`landscape.attested\` lists the
|
|
64
|
+
calls \`services/$1/model.likec4\` already declares across its boundary — those
|
|
65
|
+
are the edges the map owes, collapsed to the service; an empty list on a
|
|
66
|
+
service with no model is not licence to invent one, and the instruction says so.
|
|
54
67
|
- \`frontmatter\` — what to put in the header of every markdown artifact.
|
|
55
68
|
- \`checks[]\` — what \`loam validate\` will run. \`unchecked[]\` — what it will not.
|
|
56
69
|
2. Read \`AGENTS.md\` at the docs repo root, then walk the service — \`walk[]\` in the
|
|
@@ -70,31 +83,127 @@ thin baseline that validates is thin, not done.
|
|
|
70
83
|
tying the document to the repository. Never pad it with paths you did not read:
|
|
71
84
|
it is a record, and \`loam vouch\` hashes exactly what it names.
|
|
72
85
|
3. Write the artifacts under \`services/$1/\`, in the order the brief lists them, and
|
|
73
|
-
make the landscape edit the brief asks for
|
|
86
|
+
make the landscape edit the brief asks for — the element where none resolves, the
|
|
87
|
+
edges where the element stands alone, and the map's own housekeeping
|
|
88
|
+
\`unchecked[]\` names: a placeholder tag to clear, a curated view to add the
|
|
89
|
+
element to.
|
|
90
|
+
**model.likec4 EXTENDS the fleet map.** It declares no \`specification\` block and
|
|
91
|
+
no copy of anything the map already draws: its whole body is
|
|
92
|
+
\`model { extend <fqn> { …containers… } }\`, where \`<fqn>\` is the FULLY-QUALIFIED
|
|
93
|
+
id of the element bound to \`services/$1/\` (the brief's \`landscape.elements\` names
|
|
94
|
+
it; \`extend orderService\` resolves to nothing when the id is
|
|
95
|
+
\`marketplace.orderService\`). Kinds and tags come from
|
|
96
|
+
\`architecture/landscape.likec4\` — a kind you need and the map does not declare
|
|
97
|
+
(\`element database\`, \`element queue\`) is added THERE, once, in the same landscape
|
|
98
|
+
edit. Cross-boundary edges are written OUTSIDE the extend block, from the container
|
|
99
|
+
that makes the call to the other party's element as the map spells it; an element
|
|
100
|
+
this file declares outside its own is \`c4.element-unowned\` (warn), and which fix
|
|
101
|
+
applies depends on whose it is — a store or component this service OWNS belongs
|
|
102
|
+
INSIDE the \`extend\` block (its id becomes \`<fqn>.<name>\`), a system the service
|
|
103
|
+
merely reaches is declared in the map instead, and another service's internals
|
|
104
|
+
belong in that service's model. If you are DIFFING an existing model
|
|
105
|
+
that carries its own \`specification\`, leave it alone: that shape is still legal
|
|
106
|
+
and still parsed on its own, and \`c4.declaration-diverged\` is what reports where
|
|
107
|
+
its copies of the map's elements have drifted. Migrating one is the recipe in
|
|
108
|
+
SCHEMA.md, "Two shapes of a service model" — not something to do inside an
|
|
109
|
+
adoption you were not asked to widen.
|
|
110
|
+
A flow behind one of your arch.spec.md requirements — a \`dynamic view\`
|
|
111
|
+
over this service's own containers — goes in a \`.likec4\` beside model.likec4
|
|
112
|
+
(\`services/$1/usecases/<name>.likec4\` by convention; \`views.likec4\` reads the
|
|
113
|
+
same, because the renderer's per-service project reads every \`.likec4\` under the
|
|
114
|
+
directory and so does loam), tagged \`#req-<Requirement-ID>\` of a requirement in
|
|
115
|
+
this service's spec.md or arch.spec.md, with the tag declared in the map's
|
|
116
|
+
\`specification\` block or in a tags-only \`specification { tag req-… }\` beside the
|
|
117
|
+
\`extend\` — in exactly ONE of the two. Declared in both, the model's own project
|
|
118
|
+
holds the tag twice: the error lands on whichever document carries the second
|
|
119
|
+
declaration, as \`c4.invalid\` (an error, and the model's grading is suspended
|
|
120
|
+
behind it) when that is model.likec4, or as \`usecase.flow-invalid\` when it is the
|
|
121
|
+
sibling. Step 4 grades it. It never goes in \`architecture/usecases/\`,
|
|
122
|
+
which cannot see your containers and turns the whole fleet \`landscape.invalid\`; a
|
|
123
|
+
flow that crosses services goes there and is tagged \`#cap-\`. Never a \`#cap-\` tag
|
|
124
|
+
beside a model. Then run \`loam subsystem sync\` (from
|
|
74
125
|
this repo or the docs repo — it resolves \`docsDir\`): once the docs root carries
|
|
75
|
-
the \`likec4.config.json\` step 0's doctor asks for, it
|
|
76
|
-
\`
|
|
77
|
-
model
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
126
|
+
the \`likec4.config.json\` step 0's doctor asks for, it keeps that file's
|
|
127
|
+
\`exclude\` list covering exactly the service directories whose models stand alone —
|
|
128
|
+
so an extending model stays inside the root project, where it is the only place it
|
|
129
|
+
parses and where the renderer draws it beside the map — and writes
|
|
130
|
+
\`services/<…>/<id>/likec4.config.json\` beside each STANDALONE model that has none.
|
|
131
|
+
Both are loam's — never write one yourself, never list it in \`sources\`, never
|
|
132
|
+
diff it, never hand-edit the root \`exclude\`'s \`services/\` entries — and \`sync\`
|
|
133
|
+
leaves what it wrote untracked for you to commit. A repo whose models all extend
|
|
134
|
+
the map usually has nothing for \`sync\` to write here, and that is the healthy
|
|
135
|
+
state, not a skipped step.
|
|
81
136
|
Everything \`status: draft\`. Never write \`last_verified\`, \`sources_digest\`,
|
|
82
137
|
\`content_digest\` or \`sources_files\`.
|
|
83
|
-
4. \`loam validate --service $1 --json\`. Fix every error. \`
|
|
138
|
+
4. \`loam validate --service $1 --json\`. Fix every error. \`c4.valid\` reading
|
|
139
|
+
"extends the fleet map" is the confirmation that the model was read as one
|
|
140
|
+
document with the map; \`c4.invalid\` on this run means the model plus the map do
|
|
141
|
+
not parse together, and every error is YOURS even where the message names
|
|
142
|
+
\`architecture/landscape.likec4\` — the map parses clean on its own, or
|
|
143
|
+
\`spine.landscape-invalid\` would say so instead and this model could not be read
|
|
144
|
+
at all. \`c4.element-unowned\` (warn) is an element you declared outside the
|
|
145
|
+
element you extend — nest it inside the \`extend\` block if this service owns it,
|
|
146
|
+
declare it in the map if the service only reaches it. \`sources.unvouched\` is
|
|
84
147
|
expected on a fresh baseline — it closes when a person vouches, not when you do.
|
|
85
148
|
\`sources.unwalked\` is the walk graded against the repository: its \`details\` name
|
|
86
149
|
the top-level paths git tracks and \`sources\` never reached into. Treat each one
|
|
87
150
|
as a place to go back to, not as a line to silence — the fix is reading it, or
|
|
88
151
|
one sentence in step 6 saying why this document does not owe it.
|
|
152
|
+
\`usecase.*\` findings on this run are about the flows beside model.likec4:
|
|
153
|
+
\`usecase.step-unbacked\` is a hop no relationship in model.likec4 backs,
|
|
154
|
+
\`usecase.step-contested\` two edges backing one hop and naming different
|
|
155
|
+
operations, \`usecase.requirement-unresolved\` a \`#req-\` tag naming no
|
|
156
|
+
\`Requirement-ID:\` of this service's spec.md or arch.spec.md,
|
|
157
|
+
\`usecase.capability-unresolved\` a \`#cap-\` tag beside a model (drop it — a
|
|
158
|
+
capability is claimed on the fleet map, never inside one service), and
|
|
159
|
+
\`usecase.flow-invalid\` a file beside the model that does not read — the flows
|
|
160
|
+
were not graded, the model still was. \`usecase.step-unlinked\` never fires on a hop
|
|
161
|
+
whose caller and provider resolve to the same service — a service owes no
|
|
162
|
+
operationId to itself — but a hop from such a flow into another service's element
|
|
163
|
+
(a stand-in model.likec4 declares for a sibling, with no \`metadata { op }\`) still
|
|
164
|
+
warns here, exactly as it does on the fleet map.
|
|
89
165
|
5. \`loam validate --all --json\` in the docs repo. This is the run that grades the
|
|
90
166
|
fleet map: \`landscape.service-unmodelled\` means the element never landed,
|
|
91
167
|
\`landscape.binding-unknown\` / \`landscape.binding-duplicate\` mean it landed wrong,
|
|
92
168
|
and \`landscape.missing\` means there is no map at all. A baseline that passes step 4
|
|
93
169
|
and fails this one is documented and invisible.
|
|
94
|
-
\`service
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
170
|
+
\`landscape.service-isolated\` (a warning) means the element landed with no edge
|
|
171
|
+
anywhere in \`architecture/\` while \`services/$1/model.likec4\` declares calls across
|
|
172
|
+
its boundary — the map owes those edges. It goes quiet on the FIRST edge, so draw
|
|
173
|
+
every call the brief listed in one pass: nothing will name the ones you leave out.
|
|
174
|
+
A service whose model declares no such call and whose element
|
|
175
|
+
has no edge is SILENT here: \`landscape.touched: false\` in step 1's brief is the
|
|
176
|
+
only place that state is named, so read it before this run rather than hoping
|
|
177
|
+
for it in it.
|
|
178
|
+
Six warnings on this run are about the RENDERER's wiring rather than about any
|
|
179
|
+
document, and one command fixes four of them: \`service.model-excluded\` (the root
|
|
180
|
+
\`likec4.config.json\` excludes a directory holding a model that extends the map, so
|
|
181
|
+
the renderer never loads it), \`service.model-unexcluded\` (its mirror, and the most
|
|
182
|
+
damaging — a model that STANDS ALONE whose directory the root \`exclude\` does not
|
|
183
|
+
cover merges into the root project and duplicates every kind the map declares,
|
|
184
|
+
which blanks the whole project rather than one service),
|
|
185
|
+
\`service.likec4-config-stray\` (a project file beside an extending model, which
|
|
186
|
+
claims the model out of the root project),
|
|
187
|
+
\`service.likec4-config-missing\` (a model that stands alone with no project file of
|
|
188
|
+
its own) and, when the first four are clear, \`c4.fleet-project-invalid\` — every
|
|
189
|
+
document reads clean where loam grades it and the ONE project the renderer builds
|
|
190
|
+
out of the map plus every extending model does not parse, because something is
|
|
191
|
+
declared in two of them. The first four: run \`loam subsystem sync\` — it rewrites
|
|
192
|
+
the root \`exclude\`, writes the missing project file and DELETES the stray one. The
|
|
193
|
+
fifth is an authoring fix: the message names the file and line, and the answer is
|
|
194
|
+
to declare the tag or element once, in the map or in the single service that owns it.
|
|
195
|
+
A sixth reads the same list and is NOT one of sync's: \`landscape.excluded\` says the
|
|
196
|
+
root \`exclude\` covers \`architecture/landscape.likec4\` itself, so the renderer has no
|
|
197
|
+
map to draw at all and every extending model in the root project resolves against
|
|
198
|
+
nothing — delete or narrow that entry by hand, at the line the finding quotes. While
|
|
199
|
+
it stands, \`c4.fleet-project-invalid\` is not graded at all: that check reads the
|
|
200
|
+
renderer's project, so the one entry would come back once per model instead of once as
|
|
201
|
+
the cause. Fix it first, then re-run and read the fifth.
|
|
202
|
+
Done, stated once: step 4 clean when run from inside the service's own repo, this
|
|
203
|
+
run reporting no \`landscape.*\` finding, no \`service.*\` renderer warning and no
|
|
204
|
+
\`c4.fleet-project-invalid\`, and
|
|
205
|
+
step 1's brief re-run reporting \`landscape.touched: true\` — or, for a service that
|
|
206
|
+
truly makes and receives no call, a stated reason its element is edgeless.
|
|
98
207
|
The fleet run is never SILENT —
|
|
99
208
|
\`sources.unverifiable-from-here\` (severity ok) appears per service as a
|
|
100
209
|
confirmation, not work — so "keep going until validation is quiet" is the wrong
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/adopt.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EACT,kFAAkF;IACpF,YAAY,EAAE,cAAc;IAC5B,OAAO,EACL,iOAAiO;IACnO,UAAU,EAAE,iCAAiC;IAC7C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,+EAA+E;QAC/E,2IAA2I;QAC3I,gKAAgK;QAChK,2IAA2I;QAC3I,kIAAkI;QAClI,yNAAyN;KAC1N;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4HP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** The adoption protocol: write one service's baseline docs from its code, as draft. */\nexport const LOAM_ADOPT: CommandContent = {\n name: \"loam-adopt\",\n description:\n \"Adopt a service — write its baseline docs from its code, as draft, then validate\",\n argumentHint: \"<service-id>\",\n purpose:\n \"Write one service's baseline documentation into the docs repo from its code, as `draft`. You read the code; loam states the work and checks the result — it never reads the service, so anything you cannot show, do not write.\",\n invocation: \"loam instructions loam-adopt $1\",\n placeholders: [\"service\"],\n spine: [\n \"wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet\",\n \"`loam adopt` — the brief: the order to read the service in, every file to write, the grammar of each, and what the fleet map already says\",\n \"walk the service in the brief's order — shape first, then one surface at a time — keeping the list of every path you open, because that list becomes `sources`\",\n \"write the artifacts, everything `status: draft`, then `loam subsystem sync` for the per-service LikeC4 project file — loam's, never yours\",\n \"validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible\",\n \"hand back with what you could not determine from the code, which directories you never opened, the branch-to-scenario count per operation, and three behaviours your documents do not describe — then let a human vouch\",\n ],\n body: `Write one service's baseline documentation into the loam docs repo (its path is\n\\`docsDir\\` in ./loam.json). You read the code; loam states the work and checks the\nresult. It never reads the service — so anything you cannot show, do not write.\n\n**The bar this artifact set aims at is reproducibility**: a reader should be able\nto answer, from these files alone — what this boundary exposes, what it reaches,\nwhat shapes it exchanges, how it is run, and what pages whom — without opening\nthe code. \\`loam validate\\` grades form and joins, never depth: green means the\nfiles agree with each other, and the bar is what \"done\" is measured against — a\nthin baseline that validates is thin, not done.\n\n0. If there is no ./loam.json here, wire the repo first:\n \\`loam init --docs <path-to-docs-repo> --service $1\\` (add \\`--create\\` ONLY if the\n docs repo does not exist yet), then \\`loam doctor\\` — it resolves \\`docsDir\\`,\n checks the docs repo is one, and reports the service binding. Do not go on with\n \\`docs-missing\\`, \\`services-missing\\` or any \\`doctor.*\\` blocker standing.\n Every \\`doctor\\` finding carries a \\`fix\\` field spelling the exact command —\n type that, not an approximation of it.\n If this repo is already wired, \\`loam status --json\\` in the docs repo is the\n fastest way to see whether $1 is even the service that owes work next: its\n \\`next.adopt\\` entries name the services with nothing written down yet.\n1. \\`loam adopt --service $1 --json\\`. That output IS the brief:\n - \\`warnings[]\\` — read these FIRST. A near-miss against an existing service id\n usually means \\`$1\\` is a typo, and a brief followed to the letter then produces\n a complete, validating baseline for a service that does not exist.\n - \\`targets[]\\` — every file to write. \\`action: \"diff\"\\` means the file ALREADY EXISTS:\n read it, diff your findings against it, report what disagrees. Do not replace it.\n \\`action: \"edit\"\\` appears on exactly one target — \\`architecture/landscape.likec4\\`,\n the WHOLE FLEET's map: add this service's element and edges to it, never rewrite it.\n - \\`targets[].shape\\` — the grammar of each artifact, and \\`example\\` where one is\n shorter than a description. Every rule there is one a later check depends on;\n rules nothing checks are in \\`unchecked[]\\` instead, and are still worth following.\n - \\`landscape\\` — the elements and edges the fleet already has for this service.\n Bind to them; do not draw a second version of the same box. \\`landscape.expects\\`\n lists operations other services already call — your openapi.yaml owes them.\n \\`landscape.instruction\\` is the write the fleet map still owes this service; it\n is \\`null\\` once an element resolves to it, and only then.\n - \\`frontmatter\\` — what to put in the header of every markdown artifact.\n - \\`checks[]\\` — what \\`loam validate\\` will run. \\`unchecked[]\\` — what it will not.\n2. Read \\`AGENTS.md\\` at the docs repo root, then walk the service — \\`walk[]\\` in the\n same \\`adopt\\` output is the order, and it is an order, not a menu. Each stop\n names what to open, what to take from it, and which artifacts it feeds\n (\\`lands\\`). The first two stops fix what the service IS — how many processes,\n built from what — before any surface is enumerated: an agent that opens the\n HTTP routes first has already decided the service is an API by the time it\n meets the scheduler, and the consumer group and the outbox never get written\n down. Do not stop at the surface you recognise fastest.\n \\`walkClose\\` is the question that ends it: **list the directories you did not\n open.** Nothing downstream can find a service you documented a third of —\n \\`api.ungoverned\\` grades the operations you WROTE, and is silent about the twelve\n you never did.\n **Keep a list of every path you actually open.** That list becomes \\`sources\\` —\n written as files and directories, never glob patterns — and it is the only line\n tying the document to the repository. Never pad it with paths you did not read:\n it is a record, and \\`loam vouch\\` hashes exactly what it names.\n3. Write the artifacts under \\`services/$1/\\`, in the order the brief lists them, and\n make the landscape edit the brief asks for. Then run \\`loam subsystem sync\\` (from\n this repo or the docs repo — it resolves \\`docsDir\\`): once the docs root carries\n the \\`likec4.config.json\\` step 0's doctor asks for, it writes\n \\`services/$1/likec4.config.json\\` — and the same file beside any other service\n model in the docs repo still missing one — the LikeC4 project that makes a\n model.likec4 renderable beside the fleet map from the docs root. Those files are\n loam's — never write one yourself, never list it in \\`sources\\`, never diff it —\n and \\`sync\\` leaves them untracked for you to commit.\n Everything \\`status: draft\\`. Never write \\`last_verified\\`, \\`sources_digest\\`,\n \\`content_digest\\` or \\`sources_files\\`.\n4. \\`loam validate --service $1 --json\\`. Fix every error. \\`sources.unvouched\\` is\n expected on a fresh baseline — it closes when a person vouches, not when you do.\n \\`sources.unwalked\\` is the walk graded against the repository: its \\`details\\` name\n the top-level paths git tracks and \\`sources\\` never reached into. Treat each one\n as a place to go back to, not as a line to silence — the fix is reading it, or\n one sentence in step 6 saying why this document does not owe it.\n5. \\`loam validate --all --json\\` in the docs repo. This is the run that grades the\n fleet map: \\`landscape.service-unmodelled\\` means the element never landed,\n \\`landscape.binding-unknown\\` / \\`landscape.binding-duplicate\\` mean it landed wrong,\n and \\`landscape.missing\\` means there is no map at all. A baseline that passes step 4\n and fails this one is documented and invisible.\n \\`service.likec4-config-missing\\` (a warning) means step 3's sync did not run; the\n fix is that command.\n Done, stated once: step 4 clean when run from inside the service's own repo, and\n this run reporting no \\`landscape.*\\` finding and no \\`service.likec4-config-missing\\`.\n The fleet run is never SILENT —\n \\`sources.unverifiable-from-here\\` (severity ok) appears per service as a\n confirmation, not work — so \"keep going until validation is quiet\" is the wrong\n loop; the two runs are the test.\n6. Hand back, and say seven things:\n - what you could not determine from the code;\n - what the existing artifacts disagreed with;\n - which parts you are least sure of;\n - what you did not open — every path \\`sources.unwalked\\` named, with one line\n each on why the baseline does not owe it. That list is the only account\n anybody gets of how much of the service was actually read; loam can name the\n paths, and it can never say whether skipping one was right;\n - **two counts per operation**: how many decisions its code makes — permission\n checks, conditionally required fields, defaults and normalisations, refused\n transitions — and how many scenarios you wrote for it.\n \\`12 branches / 3 scenarios\\` is a fact a reader can act on; \"the\n requirements look complete\" is not, and no check loam has can tell the two\n apart;\n - **how well this service is covered by its own tests**, and how many of them\n became scenarios. Say the number the service's own coverage tooling\n reports, and where you read it. This is the ceiling on the whole baseline:\n a well-covered service can be documented from evidence, and a service with\n almost no tests can only be documented from a reading of its code, which is\n a weaker document however carefully it is written. Nothing in loam can\n compute this — say it, or nobody ever knows which kind they are holding;\n - **three behaviours this service has that your documents do not describe.**\n Not \"is this enough\" — that question is answered yes by every agent that\n ever wrote a baseline, including the ones that documented a third of a\n service, because the only thing it can be answered against is the document\n itself. Name three. If you looked for three and found none, say exactly\n that: it is a claim a reviewer can go and check, which \"it is complete\"\n is not.\n Then a human runs \\`loam vouch --service $1\\` in the service's own repo.\n\nAn empty docs repo's \\`loam status --json\\` names this recipe's own states as its\nfirst-hour ladder: \\`next.author-landscape\\` (the fleet map is missing, or still the\nscaffold's untouched bytes — step 3's landscape edit is what discharges it),\n\\`next.bind-service\\` (no service repository is wired to the fleet yet — step 0) and\n\\`next.adopt-first\\` (services/ is empty — this protocol, from step 1). Landing on\none of those codes means: run this recipe, starting at the step it points at.\n\nWhere the code does not say, write that it does not say. A confident sentence about\nbehaviour nobody can find is the one failure mode none of loam's checks can catch.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/adopt.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EACT,kFAAkF;IACpF,YAAY,EAAE,cAAc;IAC5B,OAAO,EACL,iOAAiO;IACnO,UAAU,EAAE,iCAAiC;IAC7C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,+EAA+E;QAC/E,2IAA2I;QAC3I,gKAAgK;QAChK,yKAAyK;QACzK,kIAAkI;QAClI,yNAAyN;KAC1N;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyOP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** The adoption protocol: write one service's baseline docs from its code, as draft. */\nexport const LOAM_ADOPT: CommandContent = {\n name: \"loam-adopt\",\n description:\n \"Adopt a service — write its baseline docs from its code, as draft, then validate\",\n argumentHint: \"<service-id>\",\n purpose:\n \"Write one service's baseline documentation into the docs repo from its code, as `draft`. You read the code; loam states the work and checks the result — it never reads the service, so anything you cannot show, do not write.\",\n invocation: \"loam instructions loam-adopt $1\",\n placeholders: [\"service\"],\n spine: [\n \"wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet\",\n \"`loam adopt` — the brief: the order to read the service in, every file to write, the grammar of each, and what the fleet map already says\",\n \"walk the service in the brief's order — shape first, then one surface at a time — keeping the list of every path you open, because that list becomes `sources`\",\n \"write the artifacts, everything `status: draft` — the model EXTENDS the fleet map — then `loam subsystem sync` for the renderer wiring, which is loam's and never yours\",\n \"validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible\",\n \"hand back with what you could not determine from the code, which directories you never opened, the branch-to-scenario count per operation, and three behaviours your documents do not describe — then let a human vouch\",\n ],\n body: `Write one service's baseline documentation into the loam docs repo (its path is\n\\`docsDir\\` in ./loam.json). You read the code; loam states the work and checks the\nresult. It never reads the service — so anything you cannot show, do not write.\n\n**The bar this artifact set aims at is reproducibility**: a reader should be able\nto answer, from these files alone — what this boundary exposes, what it reaches,\nwhat shapes it exchanges, how it is run, and what pages whom — without opening\nthe code. \\`loam validate\\` grades form and joins, never depth: green means the\nfiles agree with each other, and the bar is what \"done\" is measured against — a\nthin baseline that validates is thin, not done.\n\n0. If there is no ./loam.json here, wire the repo first:\n \\`loam init --docs <path-to-docs-repo> --service $1\\` (add \\`--create\\` ONLY if the\n docs repo does not exist yet), then \\`loam doctor\\` — it resolves \\`docsDir\\`,\n checks the docs repo is one, and reports the service binding. Do not go on with\n \\`docs-missing\\`, \\`services-missing\\` or any \\`doctor.*\\` blocker standing.\n Every \\`doctor\\` finding carries a \\`fix\\` field spelling the exact command —\n type that, not an approximation of it.\n If this repo is already wired, \\`loam status --json\\` in the docs repo is the\n fastest way to see whether $1 is even the service that owes work next: its\n \\`next.adopt\\` entries name the services with nothing written down yet.\n1. \\`loam adopt --service $1 --json\\`. That output IS the brief:\n - \\`warnings[]\\` — read these FIRST. A near-miss against an existing service id\n usually means \\`$1\\` is a typo, and a brief followed to the letter then produces\n a complete, validating baseline for a service that does not exist.\n - \\`targets[]\\` — every file to write. \\`action: \"diff\"\\` means the file ALREADY EXISTS:\n read it, diff your findings against it, report what disagrees. Do not replace it.\n \\`action: \"edit\"\\` appears on exactly one target — \\`architecture/landscape.likec4\\`,\n the WHOLE FLEET's map: add this service's element and edges to it, never rewrite it.\n It appears while nothing resolves to the service AND while the element that does\n has no edge touching it; \\`targets[].shape\\` says which of the two you are in.\n It does NOT appear when loam could not READ the map (\\`landscape.modelled: null\\`\n — a broken landscape, or a broken sibling under \\`architecture/\\`): nothing is\n known about what the map already draws, so no write to the whole fleet's map is\n briefed. \\`landscape.instruction\\` carries the answer and names the documents that\n failed; fix those and re-run.\n - \\`targets[].shape\\` — the grammar of each artifact, and \\`example\\` where one is\n shorter than a description. Every rule there is one a later check depends on;\n rules nothing checks are in \\`unchecked[]\\` instead, and are still worth following.\n - \\`landscape\\` — the elements and edges the fleet already has for this service.\n Bind to them; do not draw a second version of the same box. \\`landscape.expects\\`\n lists operations other services already call — your openapi.yaml owes them.\n \\`landscape.instruction\\` is the write the fleet map still owes this service; it\n is \\`null\\` once an element resolves to it AND at least one edge ANYWHERE in the\n \\`architecture/\\` project touches that element (\\`landscape.touched\\`), and only\n then — one edge closes the state, so draw every call in one pass.\n \\`landscape.attested\\` lists the\n calls \\`services/$1/model.likec4\\` already declares across its boundary — those\n are the edges the map owes, collapsed to the service; an empty list on a\n service with no model is not licence to invent one, and the instruction says so.\n - \\`frontmatter\\` — what to put in the header of every markdown artifact.\n - \\`checks[]\\` — what \\`loam validate\\` will run. \\`unchecked[]\\` — what it will not.\n2. Read \\`AGENTS.md\\` at the docs repo root, then walk the service — \\`walk[]\\` in the\n same \\`adopt\\` output is the order, and it is an order, not a menu. Each stop\n names what to open, what to take from it, and which artifacts it feeds\n (\\`lands\\`). The first two stops fix what the service IS — how many processes,\n built from what — before any surface is enumerated: an agent that opens the\n HTTP routes first has already decided the service is an API by the time it\n meets the scheduler, and the consumer group and the outbox never get written\n down. Do not stop at the surface you recognise fastest.\n \\`walkClose\\` is the question that ends it: **list the directories you did not\n open.** Nothing downstream can find a service you documented a third of —\n \\`api.ungoverned\\` grades the operations you WROTE, and is silent about the twelve\n you never did.\n **Keep a list of every path you actually open.** That list becomes \\`sources\\` —\n written as files and directories, never glob patterns — and it is the only line\n tying the document to the repository. Never pad it with paths you did not read:\n it is a record, and \\`loam vouch\\` hashes exactly what it names.\n3. Write the artifacts under \\`services/$1/\\`, in the order the brief lists them, and\n make the landscape edit the brief asks for — the element where none resolves, the\n edges where the element stands alone, and the map's own housekeeping\n \\`unchecked[]\\` names: a placeholder tag to clear, a curated view to add the\n element to.\n **model.likec4 EXTENDS the fleet map.** It declares no \\`specification\\` block and\n no copy of anything the map already draws: its whole body is\n \\`model { extend <fqn> { …containers… } }\\`, where \\`<fqn>\\` is the FULLY-QUALIFIED\n id of the element bound to \\`services/$1/\\` (the brief's \\`landscape.elements\\` names\n it; \\`extend orderService\\` resolves to nothing when the id is\n \\`marketplace.orderService\\`). Kinds and tags come from\n \\`architecture/landscape.likec4\\` — a kind you need and the map does not declare\n (\\`element database\\`, \\`element queue\\`) is added THERE, once, in the same landscape\n edit. Cross-boundary edges are written OUTSIDE the extend block, from the container\n that makes the call to the other party's element as the map spells it; an element\n this file declares outside its own is \\`c4.element-unowned\\` (warn), and which fix\n applies depends on whose it is — a store or component this service OWNS belongs\n INSIDE the \\`extend\\` block (its id becomes \\`<fqn>.<name>\\`), a system the service\n merely reaches is declared in the map instead, and another service's internals\n belong in that service's model. If you are DIFFING an existing model\n that carries its own \\`specification\\`, leave it alone: that shape is still legal\n and still parsed on its own, and \\`c4.declaration-diverged\\` is what reports where\n its copies of the map's elements have drifted. Migrating one is the recipe in\n SCHEMA.md, \"Two shapes of a service model\" — not something to do inside an\n adoption you were not asked to widen.\n A flow behind one of your arch.spec.md requirements — a \\`dynamic view\\`\n over this service's own containers — goes in a \\`.likec4\\` beside model.likec4\n (\\`services/$1/usecases/<name>.likec4\\` by convention; \\`views.likec4\\` reads the\n same, because the renderer's per-service project reads every \\`.likec4\\` under the\n directory and so does loam), tagged \\`#req-<Requirement-ID>\\` of a requirement in\n this service's spec.md or arch.spec.md, with the tag declared in the map's\n \\`specification\\` block or in a tags-only \\`specification { tag req-… }\\` beside the\n \\`extend\\` — in exactly ONE of the two. Declared in both, the model's own project\n holds the tag twice: the error lands on whichever document carries the second\n declaration, as \\`c4.invalid\\` (an error, and the model's grading is suspended\n behind it) when that is model.likec4, or as \\`usecase.flow-invalid\\` when it is the\n sibling. Step 4 grades it. It never goes in \\`architecture/usecases/\\`,\n which cannot see your containers and turns the whole fleet \\`landscape.invalid\\`; a\n flow that crosses services goes there and is tagged \\`#cap-\\`. Never a \\`#cap-\\` tag\n beside a model. Then run \\`loam subsystem sync\\` (from\n this repo or the docs repo — it resolves \\`docsDir\\`): once the docs root carries\n the \\`likec4.config.json\\` step 0's doctor asks for, it keeps that file's\n \\`exclude\\` list covering exactly the service directories whose models stand alone —\n so an extending model stays inside the root project, where it is the only place it\n parses and where the renderer draws it beside the map — and writes\n \\`services/<…>/<id>/likec4.config.json\\` beside each STANDALONE model that has none.\n Both are loam's — never write one yourself, never list it in \\`sources\\`, never\n diff it, never hand-edit the root \\`exclude\\`'s \\`services/\\` entries — and \\`sync\\`\n leaves what it wrote untracked for you to commit. A repo whose models all extend\n the map usually has nothing for \\`sync\\` to write here, and that is the healthy\n state, not a skipped step.\n Everything \\`status: draft\\`. Never write \\`last_verified\\`, \\`sources_digest\\`,\n \\`content_digest\\` or \\`sources_files\\`.\n4. \\`loam validate --service $1 --json\\`. Fix every error. \\`c4.valid\\` reading\n \"extends the fleet map\" is the confirmation that the model was read as one\n document with the map; \\`c4.invalid\\` on this run means the model plus the map do\n not parse together, and every error is YOURS even where the message names\n \\`architecture/landscape.likec4\\` — the map parses clean on its own, or\n \\`spine.landscape-invalid\\` would say so instead and this model could not be read\n at all. \\`c4.element-unowned\\` (warn) is an element you declared outside the\n element you extend — nest it inside the \\`extend\\` block if this service owns it,\n declare it in the map if the service only reaches it. \\`sources.unvouched\\` is\n expected on a fresh baseline — it closes when a person vouches, not when you do.\n \\`sources.unwalked\\` is the walk graded against the repository: its \\`details\\` name\n the top-level paths git tracks and \\`sources\\` never reached into. Treat each one\n as a place to go back to, not as a line to silence — the fix is reading it, or\n one sentence in step 6 saying why this document does not owe it.\n \\`usecase.*\\` findings on this run are about the flows beside model.likec4:\n \\`usecase.step-unbacked\\` is a hop no relationship in model.likec4 backs,\n \\`usecase.step-contested\\` two edges backing one hop and naming different\n operations, \\`usecase.requirement-unresolved\\` a \\`#req-\\` tag naming no\n \\`Requirement-ID:\\` of this service's spec.md or arch.spec.md,\n \\`usecase.capability-unresolved\\` a \\`#cap-\\` tag beside a model (drop it — a\n capability is claimed on the fleet map, never inside one service), and\n \\`usecase.flow-invalid\\` a file beside the model that does not read — the flows\n were not graded, the model still was. \\`usecase.step-unlinked\\` never fires on a hop\n whose caller and provider resolve to the same service — a service owes no\n operationId to itself — but a hop from such a flow into another service's element\n (a stand-in model.likec4 declares for a sibling, with no \\`metadata { op }\\`) still\n warns here, exactly as it does on the fleet map.\n5. \\`loam validate --all --json\\` in the docs repo. This is the run that grades the\n fleet map: \\`landscape.service-unmodelled\\` means the element never landed,\n \\`landscape.binding-unknown\\` / \\`landscape.binding-duplicate\\` mean it landed wrong,\n and \\`landscape.missing\\` means there is no map at all. A baseline that passes step 4\n and fails this one is documented and invisible.\n \\`landscape.service-isolated\\` (a warning) means the element landed with no edge\n anywhere in \\`architecture/\\` while \\`services/$1/model.likec4\\` declares calls across\n its boundary — the map owes those edges. It goes quiet on the FIRST edge, so draw\n every call the brief listed in one pass: nothing will name the ones you leave out.\n A service whose model declares no such call and whose element\n has no edge is SILENT here: \\`landscape.touched: false\\` in step 1's brief is the\n only place that state is named, so read it before this run rather than hoping\n for it in it.\n Six warnings on this run are about the RENDERER's wiring rather than about any\n document, and one command fixes four of them: \\`service.model-excluded\\` (the root\n \\`likec4.config.json\\` excludes a directory holding a model that extends the map, so\n the renderer never loads it), \\`service.model-unexcluded\\` (its mirror, and the most\n damaging — a model that STANDS ALONE whose directory the root \\`exclude\\` does not\n cover merges into the root project and duplicates every kind the map declares,\n which blanks the whole project rather than one service),\n \\`service.likec4-config-stray\\` (a project file beside an extending model, which\n claims the model out of the root project),\n \\`service.likec4-config-missing\\` (a model that stands alone with no project file of\n its own) and, when the first four are clear, \\`c4.fleet-project-invalid\\` — every\n document reads clean where loam grades it and the ONE project the renderer builds\n out of the map plus every extending model does not parse, because something is\n declared in two of them. The first four: run \\`loam subsystem sync\\` — it rewrites\n the root \\`exclude\\`, writes the missing project file and DELETES the stray one. The\n fifth is an authoring fix: the message names the file and line, and the answer is\n to declare the tag or element once, in the map or in the single service that owns it.\n A sixth reads the same list and is NOT one of sync's: \\`landscape.excluded\\` says the\n root \\`exclude\\` covers \\`architecture/landscape.likec4\\` itself, so the renderer has no\n map to draw at all and every extending model in the root project resolves against\n nothing — delete or narrow that entry by hand, at the line the finding quotes. While\n it stands, \\`c4.fleet-project-invalid\\` is not graded at all: that check reads the\n renderer's project, so the one entry would come back once per model instead of once as\n the cause. Fix it first, then re-run and read the fifth.\n Done, stated once: step 4 clean when run from inside the service's own repo, this\n run reporting no \\`landscape.*\\` finding, no \\`service.*\\` renderer warning and no\n \\`c4.fleet-project-invalid\\`, and\n step 1's brief re-run reporting \\`landscape.touched: true\\` — or, for a service that\n truly makes and receives no call, a stated reason its element is edgeless.\n The fleet run is never SILENT —\n \\`sources.unverifiable-from-here\\` (severity ok) appears per service as a\n confirmation, not work — so \"keep going until validation is quiet\" is the wrong\n loop; the two runs are the test.\n6. Hand back, and say seven things:\n - what you could not determine from the code;\n - what the existing artifacts disagreed with;\n - which parts you are least sure of;\n - what you did not open — every path \\`sources.unwalked\\` named, with one line\n each on why the baseline does not owe it. That list is the only account\n anybody gets of how much of the service was actually read; loam can name the\n paths, and it can never say whether skipping one was right;\n - **two counts per operation**: how many decisions its code makes — permission\n checks, conditionally required fields, defaults and normalisations, refused\n transitions — and how many scenarios you wrote for it.\n \\`12 branches / 3 scenarios\\` is a fact a reader can act on; \"the\n requirements look complete\" is not, and no check loam has can tell the two\n apart;\n - **how well this service is covered by its own tests**, and how many of them\n became scenarios. Say the number the service's own coverage tooling\n reports, and where you read it. This is the ceiling on the whole baseline:\n a well-covered service can be documented from evidence, and a service with\n almost no tests can only be documented from a reading of its code, which is\n a weaker document however carefully it is written. Nothing in loam can\n compute this — say it, or nobody ever knows which kind they are holding;\n - **three behaviours this service has that your documents do not describe.**\n Not \"is this enough\" — that question is answered yes by every agent that\n ever wrote a baseline, including the ones that documented a third of a\n service, because the only thing it can be answered against is the document\n itself. Name three. If you looked for three and found none, say exactly\n that: it is a claim a reviewer can go and check, which \"it is complete\"\n is not.\n Then a human runs \\`loam vouch --service $1\\` in the service's own repo.\n\nAn empty docs repo's \\`loam status --json\\` names this recipe's own states as its\nfirst-hour ladder: \\`next.author-landscape\\` (the fleet map is missing, or still the\nscaffold's untouched bytes — step 3's landscape edit is what discharges it),\n\\`next.bind-service\\` (no service repository is wired to the fleet yet — step 0) and\n\\`next.adopt-first\\` (services/ is empty — this protocol, from step 1). Landing on\none of those codes means: run this recipe, starting at the step it points at.\n\nWhere the code does not say, write that it does not say. A confident sentence about\nbehaviour nobody can find is the one failure mode none of loam's checks can catch.\n`,\n};\n"]}
|
|
@@ -62,7 +62,10 @@ and say it.
|
|
|
62
62
|
|---|---|---|
|
|
63
63
|
| \`service.unknown\` | no \`services/<id>/\` at all — the id is a typo until proven otherwise | use one of the ids the message offers, or \`loam list services\`; never \`loam adopt\` a misspelling |
|
|
64
64
|
| \`service.no-model\` | the directory is real but there is no model.likec4 | run \`loam adopt\` for it — without the C4 center nothing else has anywhere to hang |
|
|
65
|
-
| \`c4.invalid\` | model.likec4 does not parse | fix this first — an unreadable axis makes every other check meaningless |
|
|
65
|
+
| \`c4.invalid\` | model.likec4 does not parse. How it was READ depends on the file's own grammar (SCHEMA.md, "Two shapes of a service model"): a model declaring its own \`specification\` is parsed ALONE, exactly as before; a model with none EXTENDS the fleet map and is parsed together with \`architecture/landscape.likec4\`. In the second case every error belongs to the model even when the message names the map — the map parsed clean by itself, or \`spine.landscape-invalid\` would be standing here instead and this model could not be read at all | fix this first — an unreadable axis makes every other check meaningless. For an extending model the usual causes are a kind the map does not declare (add \`element database\` to the map's one \`specification\` block), an \`extend\` naming a partial id (it takes the FULLY-QUALIFIED id — \`marketplace.orderService\`, never \`orderService\`), or a \`description\` written inside the \`extend\` block, which belongs on the map's declaration. A model that declares an element kind AND writes \`extend\` is the standalone shape with an \`extend\` that can never resolve; the details say so and name the two lines, one of which has to go |
|
|
66
|
+
| \`landscape.service-unmodelled\` | the same code as the fleet row below, raised on the SERVICE target when this service's model.likec4 EXTENDS the fleet map and no element in \`architecture/\` resolves to \`services/<id>/\`. An extending model says what is INSIDE the element the map binds; with no such element there is nothing to be inside, so no count in the model means anything and neither \`c4.valid\` nor \`c4.invalid\` is claimed. A model that STANDS ALONE never reaches it here — it answers for its own elements, and the fleet run owns that state | draw the element, or bind an existing one with \`metadata { service '<id>' }\`; then re-run |
|
|
67
|
+
| \`c4.declaration-diverged\` (warn) | a model that STANDS ALONE declares an element the fleet map (\`architecture/\`) also declares, and the two copies disagree about \`kind\`, \`title\`, the tag SET or the \`metadata { service }\` binding. The two declarations are joined by id, then by the \`metadata { service }\` binding both wrote, then by the map element whose id ends with the model's as a dotted tail — so a map that GROUPS its services under a parent is compared like any other, and an ambiguous tail is compared with nothing. A tail that is a BARE last segment (\`db\`, \`api\`, \`agent\`) joins only a FLEET-LEVEL map element — one no ancestor of which stands for a service, so a top-level element or a box the map draws inside a plain grouping element, never an element drawn inside a service's own — because one service's private store and another service's store are two elements owned by two services, not one element declared twice, while a box the map draws at fleet level and a model re-declares at top level is one name given two meanings. Two documents are two authorities on one element, and until now nothing read them together: a copy losing \`#platform\` changes what every view filtering on that tag shows, and no check said so. One finding per divergent element, naming both declarations and which fields differ; \`description\` is deliberately NOT compared, because it is prose nothing grades, while the other four each change what a view shows or what loam resolves. Silent while the map does not parse, and impossible for a model that extends the map — there is one declaration | copy the map's declaration verbatim into the model, treating the map as the authority; or migrate the model to the extending shape (the ordered recipe is in SCHEMA.md, "Two shapes of a service model"), after which the element is declared once and cannot diverge. When the message says copying the map's declaration would not clear it, the difference comes off \`specification { element <kind> { … } }\` and no copy can fix it — the message names the document whose specification declares tags on that kind, and the repair is to match the two blocks there, or to migrate to the extending shape so there is one specification and one declaration |
|
|
68
|
+
| \`c4.element-unowned\` (warn) | a model that EXTENDS the fleet map declares an element outside the element that resolves to this service — one finding per element, with its id and kind. An extending model adds elements under its own element and nowhere else: what it adds elsewhere lands in the fleet project as this service's private copy of somebody else's box | a store or component this service OWNS goes inside the \`extend <fqn> { }\` block, where its id becomes \`<fqn>.<name>\` — that is the usual answer and the one the old wording omitted. Otherwise: declare a system this service merely REACHES in \`architecture/landscape.likec4\`, once, and point an edge at it from the container that reaches it; put another service's internals in that service's own model |
|
|
66
69
|
| \`c4.no-relationships\` (warn) | the model parses, declares elements, and no relationship joins anything — while its own evidence says it should reach something: more than one nested element, or dependencies declared in this service's health.yaml. A model that reaches nothing is almost never true; without this check, a 2-element model with 0 relationships and a 19-element model with 19 relationships could receive the same green verdict | draw the edges the service actually has — what its containers call, what reads and writes its stores, what it depends on at runtime. A bare root-plus-one-container baseline with no other evidence stays silent on purpose: nothing proves it thin |
|
|
67
70
|
| \`requirements.missing-scenarios\` | a requirement with no scenario | add the acceptance criteria; do not delete the requirement |
|
|
68
71
|
| \`requirements.stepless-scenario\` | a scenario with a heading and no recognizable steps — it satisfies the coverage rule and tests nothing: cucumber runs it vacuously green and \`loam verify --results\` can never confirm it. Graded on spec.md and arch.spec.md alike, in \`--service\` and \`--feature\` scope both, because a stepless scenario in a delta merges into the living spec and is called covered forever after | write the body as \`- **Given/When/Then**\` bullets |
|
|
@@ -96,7 +99,7 @@ and say it.
|
|
|
96
99
|
| \`api.ungoverned\` (warn) | operation(s) no requirement's \`Operations:\` line names | write the requirement, or link an existing one |
|
|
97
100
|
| \`api.ops-unlinked\` (warn) | operations AND requirements exist but zero \`Operations:\` lines join them — the API axis is vacuously green | link each requirement to the operations it governs |
|
|
98
101
|
| \`api.requirement-deprecated\` (warn) | a requirement's \`Operations:\` list resolves only to operations the OpenAPI marks \`deprecated: true\` — the behaviour it governs is on its way out | migrate the requirement to the replacement operation, or retire it with the ops it governs |
|
|
99
|
-
| \`spine.landscape-invalid\` | the
|
|
102
|
+
| \`spine.landscape-invalid\` | the \`architecture/\` project does not parse, so the C4↔API spine cannot be checked. The whole project, not the landscape file alone: a \`architecture/usecases/x.likec4\` that does not read reports this on a single-service run exactly as under \`--all\`. For a service whose model EXTENDS the map the message carries one clause more — the model cannot be read either, so neither \`c4.valid\` nor \`c4.invalid\` is emitted for it and every model-dependent check on that service is suspended rather than failing. The message names the broken document(s) repo-relative and \`locations[0]\` points at the first of them | fix the file the message names, under \`architecture/\`, before anything else — one unreadable document there suspends the cross-service layer for the whole fleet |
|
|
100
103
|
| \`spine.op-undefined\` | a landscape edge calls an operation this service's OpenAPI does not define | a broken contract between services — fix the edge or add the endpoint |
|
|
101
104
|
| \`spine.op-link-missing\` (warn) | a landscape "Calls" edge into this service with no \`metadata { op }\` | link it to the operationId |
|
|
102
105
|
| \`spine.op-deprecated\` (warn) | a landscape edge calls an operation this service's OpenAPI marks \`deprecated: true\` — the consumer is standing on a contract being retired | migrate the consumer to the replacement operation; deprecation is the first step of retiring an op, and the op stays defined until a human removes it |
|
|
@@ -114,6 +117,12 @@ and say it.
|
|
|
114
117
|
| \`event.messages-unlinked\` (warn) | messages AND requirements exist but zero \`Publishes:\`/\`Consumes:\` lines join them — the event axis is vacuously green | link each requirement to the messages it governs |
|
|
115
118
|
| \`event.ungoverned\` (warn) | message(s) declared in asyncapi.yaml that no requirement's \`Publishes:\`/\`Consumes:\` line names — \`api.ungoverned\`'s event twin, and the per-message half \`event.messages-unlinked\` cannot see: that code stays silent once even one message is linked | write the requirement that governs each named message, or drop the declaration |
|
|
116
119
|
| \`covers.unknown\` (warn) | a \`Covers:\` entry in arch.spec.md resolves to no element, edge, alert or SLI — living and feature deltas alike | fix the id (the message offers close ones); a mistyped entry silently costs the coverage it was written for |
|
|
120
|
+
| \`usecase.step-unbacked\` | a hop of a \`#req-\`-tagged \`dynamic view\` in this service's own LikeC4 project — any \`.likec4\` beside model.likec4, or a view inside it — that NO relationship in model.likec4 backs. LikeC4 reports no error for it, so the diagram renders and the renderer's project parses; only this convicts it. The message reads \`<svc>: services/<tree>/<file> — dynamic view '<id>' step N\` | draw the edge in \`model { }\` (with \`metadata { op '<operationId>' }\` where it is a call), or, if the hop is a return step, write it as \`<target> <- <source>\` |
|
|
121
|
+
| \`usecase.step-contested\` (warn) | two or more relationships in model.likec4 back one hop of a service-local flow and name DIFFERENT operations — an absent \`metadata { op }\` counts as a value — so nothing can say which one the hop exercises; \`details\` lists every candidate | give the candidates one operation, or draw the hop between the elements that name exactly one |
|
|
122
|
+
| \`usecase.step-unlinked\` (warn) | the same code as the fleet row below, raised on a service-local flow: the relationship backing a hop of a \`#req-\`-tagged \`dynamic view\` beside model.likec4 carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's caller is not a person and its target resolves to a real \`services/<id>/\` — the stand-in a model declares for a sibling service is the case that reaches it here. Never between two containers of THIS service: a service owes no operationId to itself. The \`--all\` row spells out all four mechanical guards | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
|
|
123
|
+
| \`usecase.requirement-unresolved\` | a \`#req-<slug>\` tag on a flow beside model.likec4 whose slug names no \`Requirement-ID\` of THIS service — scoped by the directory, never by a \`#cap-\` tag: the ids of spec.md and arch.spec.md together. Four arms: neither document exists, neither declares a \`Requirement-ID:\`, none flattens to the slug (close ids offered as the tag to write), or two flatten alike — a Requirement-ID is unique inside ONE document and this service has two | add \`Requirement-ID: <id>\` to the requirement this flow keeps, or fix the slug; for two colliding ids, rename one |
|
|
124
|
+
| \`usecase.capability-unresolved\` | a \`#cap-\` tag on a \`dynamic view\` inside this service's own project, where no capability can be claimed — a capability is a fleet promise realized by a hop sequence across the fleet map, and this flow's hops are this service's containers. Fires whether or not \`architecture/capabilities.yaml\` exists: the reason is placement, not vocabulary. The view stays opted in and its steps are graded | move the flow to \`architecture/usecases/\` if it crosses services, else drop the \`#cap-\` tag and keep the \`#req-\` one |
|
|
125
|
+
| \`usecase.flow-invalid\` | a \`.likec4\` beside model.likec4 that does not parse as part of the service's LikeC4 project — an unresolved element, a syntax error, or a \`specification\` block the project already has (that is a duplicate whether the model declares one or the fleet map does). Every \`.likec4\` under the service directory is read as ONE project the way the renderer reads it — for an extending model that project is the fleet map plus those documents — and the renderer refuses the whole project over one broken file; no flow in it was graded. model.likec4 itself still parses and is graded (\`c4.valid\`, \`Covers:\`, health) — this is never \`c4.invalid\`. A sibling the service's OWN \`likec4.config.json\` \`exclude\` covers is not in that project and is not graded | fix the file the finding names (\`locations[0]\`, and one \`details\` line per error prefixed by its file) — the renderer refuses the same project until it reads. An undeclared reserved tag adds one further \`details\` line naming the \`tag <name>\` declaration to write and the \`specification { }\` block it belongs in |
|
|
117
126
|
| \`api.response-ungoverned\` (warn) | an operation declares a 4xx/5xx response that no scenario of any requirement governing it reaches. \`api.ungoverned\` grades whole OPERATIONS, so an endpoint with one happy-path requirement and twelve declared failure codes was fully governed by every other check — and the refusals are where the decision layer lives | write the refusal as a scenario, or as a row in an existing one. A scenario reaches a code when the number appears in its body, \`Examples\` rows included |
|
|
118
127
|
| \`obligation.unknown\` | a \`#obl-<name>\` tag on the LIVING fleet map that \`architecture/obligations.yaml\` does not declare. An ERROR, and the asymmetry with its sibling below is the point: a tag that resolves to nothing reads exactly like a rule the fleet keeps, in review and in every derived view. \`details\` names every object carrying it | fix the tag (the message offers close names), or declare the obligation |
|
|
119
128
|
| \`obligation.unapplied\` (warn) | a declared obligation that no \`#obl-\` tag applies anywhere on the map — the mirror of \`permissions.unenforced\`, and it earns its place for the same reason: a vocabulary is worth what cites it. Either a decision that was reversed and left its word behind, or one nobody has placed yet | tag the elements and edges it governs, or drop the declaration |
|
|
@@ -133,21 +142,13 @@ and say it.
|
|
|
133
142
|
| \`capability.realizes-unknown\` | a \`Realizes:\` entry in spec.md or arch.spec.md — living at the service target, or a feature's delta of either, where it gates \`loam archive\` (\`--approve\` overrides) — that names no capability requirement. Five distinct failures, each with its own fix in the message: the entry is not spelled \`<capability-id>#<Requirement-ID>\`; the capability is undeclared; it is declared but has no document; the document declares no requirements yet; or it declares requirements but not this id. Silent under the same ladder as \`capability.unknown\` | follow the message — it names which of the five happened, and offers close names where there are any |
|
|
134
143
|
| \`capability.realizes-stale\` (warn) | a PINNED \`Realizes:\` entry whose capability requirement has been rewritten since the pin was stamped. The join still resolves — the address is fine and the promise behind it moved, which no existence check can see. Only pinned entries are graded, so a corpus that has never run \`loam rebase --living\` produces none of these, ever | re-read \`capabilities/<id>/spec.md\` and decide which side is now wrong: change this requirement, change the promise, or — when this requirement is still right as it stands — \`loam rebase --living\` to record the re-read |
|
|
135
144
|
| \`capability.requirement-unrealized\` (warn) | a requirement in a capability document that no living service requirement's \`Realizes:\` line names, one warning PER requirement, subject = the \`<capability>#<id>\` entry an author would type to fix it. The sharper sibling of \`capability.unrealized\`, and the one that survives a healthy-looking row: a capability with four requirements and three realized reports nothing at all through the other code | add the line to each service requirement that carries part of the promise — or leave it, if the promise is genuinely not implemented yet: writing the business document ahead of the fleet is the intended use, which is why this warns and never gates |
|
|
136
|
-
| \`capability.uncovered\` (warn, GATES archive) | a requirement a feature's \`features/<FEAT>/capabilities/<id>/spec.md\` ADDS that no ADDED or MODIFIED requirement in the same feature's \`specs/\` deltas names with \`Realizes: <capability-id>#<Requirement-ID>\`, subject = the capability id. A WARNING because the document is legal — writing a promise ahead of the fleet is the intended use — that GATES because the merge is what is unsafe: once it lands, the only thing that will ever mention the promise again is \`capability.requirement-unrealized\`, a fleet-scope warning nobody reads. MODIFIED is exempt (the promise may already be realized by living text), and a requirement with no \`Requirement-ID:\` is left to \`capability.requirement-unidentified\` alone | add the \`Realizes:\` line to the service requirement that carries part of the promise; or, if the promise CROSSES services, write the flow that keeps it as \`features/<FEAT>/usecases/<name>.likec4\` — a views-only document holding one \`dynamic view\` tagged \`#cap-<capability>\` and \`#req-<requirement>\`, which \`loam archive\` merges into \`architecture/usecases/\` and \`loam unarchive\` takes back; or \`loam archive <FEAT> --approve\` to land the promise ahead of the fleet and let \`capability.requirement-unrealized\` carry it |
|
|
137
|
-
| \`usecase.flow-invalid\` | a \`features/<FEAT>/usecases/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, a hop naming an element the merge does not land, or a landscape merge that itself refuses. The flows were NOT graded, and the archive would copy them into \`architecture/\` for the next reader's \`loam validate --all\` to fail on. \`--approve\` does NOT move it (\`overridable: false\`): that flag overrides loam's judgment about coherence, never its ability to read an axis | fix what the message names. A hop into an element the feature adds is legal — the flow is graded against the POST-MERGE map — so the usual cause is a spelling that matches neither the living landscape nor \`delta.likec4\` |
|
|
138
|
-
| \`capability.remove-requirement-realized\` | the same join in the removal direction: a feature's capability delta REMOVES a requirement that something the merge leaves behind still names with \`Realizes:\` — a living service requirement this feature does not also change, or one of its own ADDED/MODIFIED requirements. Without it the archive succeeded and the very next \`loam validate --all\` failed with \`capability.realizes-unknown\` against a service document nobody in the feature had touched. An ERROR, exactly as \`openapi.remove-op-consumed\` is for the identical shape one axis over; \`--approve\` overrides | retire the realizing requirement in the same feature, or drop its \`Realizes:\` line (a MODIFIED does that by restating the requirement without it), or \`loam archive <FEAT> --approve\` to break the join deliberately |
|
|
139
|
-
| \`glossary.term-exists\` | a \`features/<FEAT>/glossary/<term>.md\` whose term the LIVING glossary already defines. The merge is a whole-file copy, so it would replace an authored definition wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because approving it is approving a deletion nobody described. A feature-local glossary document INTRODUCES a term; it does not rewrite one | delete the feature's copy and edit \`glossary/<term>.md\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
|
|
140
|
-
| \`usecase.flow-exists\` | a \`features/<FEAT>/usecases/<name>.likec4\` whose flow the LIVING \`architecture/usecases/\` already holds. The merge is a whole-file copy, so it would replace an authored hop sequence wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because a \`dynamic view\`'s ordered steps are written down in no other document the fleet keeps. A feature-local flow INTRODUCES a use case; it does not rewrite one | delete the feature's copy and edit \`architecture/usecases/<name>.likec4\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
|
|
141
|
-
| \`deployment.doc-exists\` | a \`features/<FEAT>/deployment/<name>.likec4\` whose file the LIVING \`architecture/\` already holds. The merge is a whole-file copy, so it would replace an authored topology wholesale, and \`--approve\` does NOT move it — the same act, and the same severity, as \`usecase.flow-exists\` one axis over. The collision is on the FILE, never on what is inside it: two features may both \`extend\` one living region from documents of their own and both archive | delete the feature's copy and edit \`architecture/<name>.likec4\` directly in the same change — or, to ADD to what that file declares, rename the feature's document and say \`extend\` in it |
|
|
142
|
-
| \`deployment.doc-invalid\` | a \`features/<FEAT>/deployment/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The corpus is the feature's \`deployment/\` PLUS the living \`architecture/\` documents it \`extend\`s, so the fault may be in either and every message names its file. \`--approve\` does not move it: the flag overrides loam's judgement about coherence, never its ability to read an axis | fix what the messages name. A document instancing an element only this feature's \`delta.likec4\` adds is LEGAL and resolves — that is what the merge preview is for; one naming an element the merge does not land is the case this refuses |
|
|
143
|
-
| \`deployment.doc-reserved\` | a \`features/<FEAT>/deployment/<name>.likec4\` whose name loam owns inside \`architecture/\`: \`subsystems.likec4\` (generated, overwritten wholesale by \`loam subsystem sync\`, and parsed by nothing — so topology landing there is invisible from the moment it archives), \`landscape.likec4\` (spliced into, never replaced), or any path under \`usecases/\` (the flow tree). Refused whether or not the target exists, which is the point: two of the three fail silently when it does not | rename the feature's document — its name is yours to choose, and \`extend\` is how it adds to what a living document already declares |
|
|
144
145
|
| \`glossary.unlinked\` (warn) | a term defined in \`glossary/<term>.md\` that no document OUTSIDE \`glossary/\` links to — the mirror of \`permissions.unenforced\` and \`capability.unrealized\`, and it earns its place for their reason: a vocabulary is worth what cites it. A term cited only by OTHER TERMS is still unlinked and the citing terms are listed in \`details\` — a glossary is a network of definitions, so \`order-line\` linking to \`order\` says the glossary is consistent, not that the fleet uses the word. When NOTHING outside cites anything it is one finding for the whole glossary rather than one per term, because that is one afternoon's work and not N breaches. Warn and never a gate: writing the vocabulary ahead of the fleet is the intended use | cite the term from the requirement, capability or intent that uses it — \`[Order](../../glossary/order.md)\` — or delete the definition. \`loam list glossary\` shows every term with the documents that cite it |
|
|
145
146
|
| \`link.unresolved\` | a markdown link in an authored document whose relative target names nothing. ONE finding per document, listing every broken link with its line, because a rotted set of links is one editing session rather than ten separate breaches. The corpus is every authored markdown document: both spec axes living and delta, \`runbook.md\`, every \`adrs/\` file at all three altitudes, \`intent.md\`, and each capability document — the fleet's own ADRs and living capability tree are graded once, on the \`landscape\` target, and never repeated per service. Three things are deliberately NOT graded, each of them a question loam has no answer to: a target outside the docs repo (that tree may not be checked out beside these documents), a bare \`#section\` anchor, and anything inside a fenced block or an inline code span. CASE **is** graded — a link resolves only when each path segment is spelled the way the directory stores it, and when only the case is wrong the finding names the stored spelling, so a link that works on a Windows laptop and 404s on GitHub is caught here rather than in review | fix the path, or write the document it points at. A link here is a join of the same kind as \`Operations:\` and \`Covers:\` — write it as a relative path, which is the only form that can resolve |
|
|
146
147
|
| \`link.unreadable\` | a document in that corpus could not be decoded — almost always UTF-16, which PowerShell's \`>\` and \`Out-File\` write unasked. Its links were not checked; everything else about the service or feature still was, which is exactly why this is not \`service.unreadable\` | re-save the file as UTF-8 (\`Out-File -Encoding utf8\`) |
|
|
147
148
|
| \`docs.commit-interrupted\` | a \`.loam-commit\` journal sits in the docs repo: a writer's commit never finished, so the living docs may be half-merged — nothing graded in the same run can be trusted to describe one state, and a green here would certify bytes no run produced | run the command the finding names (it recovers first), or \`loam doctor\` for the file-by-file account; the finding leads every validate mode until the journal is gone |
|
|
148
149
|
| \`health.invalid\` (warn) | health.yaml exists but does not parse — alert/SLI ids are unreadable, so \`Covers: alert:/sli:\` entries and health coverage are unchecked (a missing health.yaml is legal and silent; an unreadable one used to masquerade as \`covers.unknown\` typos) | fix the YAML — the health axis resumes once it reads |
|
|
149
150
|
| \`health.uncovered\` (warn) | health.yaml declares an alert or SLI no arch.spec.md requirement covers | write the arch requirement with \`Covers: alert:<id>\` / \`Covers: sli:<id>\` — a signal nothing tests is dashboard decoration |
|
|
150
|
-
| \`health.dependency-unmodelled\` (warn) | a \`dependencies:\` id in health.yaml that nothing in this service's OWN model.likec4 answers to — by element id, \`metadata { service }\` binding, or title. Resolved against the service model, never the
|
|
151
|
+
| \`health.dependency-unmodelled\` (warn) | a \`dependencies:\` id in health.yaml that nothing in this service's OWN model.likec4 answers to — by element id, \`metadata { service }\` binding, or title, matched exactly and never by suffix. Resolved against the service model, never the fleet map read on its own: the model carries everything the service touches (private stores included, as nested containers), and the on-call file must name the same world. For a model that EXTENDS the map the set is its own SLICE — the elements it adds, plus every endpoint of its own edges and each endpoint's ANCESTORS — so \`kafka\` answers through an edge that names \`kafka.orderEvents\`, and a peer service answers through the binding on the map's element the model points at. Either a dependency nobody modelled, or one that no longer exists — for example, a disabled optional service registry left as \`critical: startup\` under a green validator | model it (a system the service reaches as an element of the FLEET MAP with the real edge drawn from the container that reaches it; a private store as a nested container), correct the id to one of the element's three names — the service id or a top-level system id is the spelling that survives either model shape — or delete the entry |
|
|
151
152
|
| \`gherkin.missing\` (warn) | a living scenario no generated .feature scenario carries a digest for — the suite has no test for these words. Fires only in the service's repo, once \`<gherkinDir>/loam/\` exists | \`loam gherkin --service <id>\` regenerates the suite |
|
|
152
153
|
| \`gherkin.stale\` (warn) | a generated scenario's digest matches no living scenario while its requirement still exists — the spec moved under the file (a reworded scenario reports stale + missing together) | regenerate; never edit a generated file to catch it up |
|
|
153
154
|
| \`gherkin.orphaned\` (warn) | a generated file whose requirement no longer exists in the living spec (a file for a feature still in flight is exempt — it answers to its feature until it archives) | regenerate; the orphaned file is deleted and reported |
|
|
@@ -208,6 +209,14 @@ what lets a feature ship:
|
|
|
208
209
|
| \`intent.empty\` (warn, gates archive) | intent.md is missing, or its Why/Scope hold nothing outside the scaffold's own comments | write why the feature exists — it is the first thing a reviewer reads. \`--approve\` overrides |
|
|
209
210
|
| \`archedge.uncovered\` (warn) | no scenario names a tagged edge (a heuristic) | write the scenario, or say why the edge needs none |
|
|
210
211
|
| \`c4.uncovered\` (warn) | a NEW tagged element or edge in delta.likec4 that no arch requirement covers via \`Covers:\` — the mechanical check, not the heuristic above | add the requirement to \`specs/<svc>/arch.spec.md\` (outbox? retries? alerts?), or the architectural obligations ship unchecked |
|
|
212
|
+
| \`capability.uncovered\` (warn, GATES archive) | a requirement a feature's \`features/<FEAT>/capabilities/<id>/spec.md\` ADDS that no ADDED or MODIFIED requirement in the same feature's \`specs/\` deltas names with \`Realizes: <capability-id>#<Requirement-ID>\`, subject = the capability id. A WARNING because the document is legal — writing a promise ahead of the fleet is the intended use — that GATES because the merge is what is unsafe: once it lands, the only thing that will ever mention the promise again is \`capability.requirement-unrealized\`, a fleet-scope warning nobody reads. MODIFIED is exempt (the promise may already be realized by living text), and a requirement with no \`Requirement-ID:\` is left to \`capability.requirement-unidentified\` alone | add the \`Realizes:\` line to the service requirement that carries part of the promise; or, if the promise CROSSES services, write the flow that keeps it as \`features/<FEAT>/usecases/<name>.likec4\` — a views-only document holding one \`dynamic view\` tagged \`#cap-<capability>\` and \`#req-<requirement>\`, which \`loam archive\` merges into \`architecture/usecases/\` and \`loam unarchive\` takes back; or \`loam archive <FEAT> --approve\` to land the promise ahead of the fleet and let \`capability.requirement-unrealized\` carry it |
|
|
213
|
+
| \`usecase.flow-invalid\` | a \`features/<FEAT>/usecases/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, a hop naming an element the merge does not land, or a landscape merge that itself refuses. The flows were NOT graded, and the archive would copy them into \`architecture/\` for the next reader's \`loam validate --all\` to fail on. \`--approve\` does NOT move it (\`overridable: false\`): that flag overrides loam's judgment about coherence, never its ability to read an axis. Also fires on the SERVICE target, under \`--service\` and per service under \`--all\`, for a \`.likec4\` beside model.likec4 that does not read as part of that service's own project — there the flows beside the model were not graded while model.likec4 itself still was | fix what the message names. A hop into an element the feature adds is legal — the flow is graded against the POST-MERGE map — so the usual cause is a spelling that matches neither the living landscape nor \`delta.likec4\` |
|
|
214
|
+
| \`capability.remove-requirement-realized\` | the same join in the removal direction: a feature's capability delta REMOVES a requirement that something the merge leaves behind still names with \`Realizes:\` — a living service requirement this feature does not also change, or one of its own ADDED/MODIFIED requirements. Without it the archive succeeded and the very next \`loam validate --all\` failed with \`capability.realizes-unknown\` against a service document nobody in the feature had touched. An ERROR, exactly as \`openapi.remove-op-consumed\` is for the identical shape one axis over; \`--approve\` overrides | retire the realizing requirement in the same feature, or drop its \`Realizes:\` line (a MODIFIED does that by restating the requirement without it), or \`loam archive <FEAT> --approve\` to break the join deliberately |
|
|
215
|
+
| \`glossary.term-exists\` | a \`features/<FEAT>/glossary/<term>.md\` whose term the LIVING glossary already defines. The merge is a whole-file copy, so it would replace an authored definition wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because approving it is approving a deletion nobody described. A feature-local glossary document INTRODUCES a term; it does not rewrite one | delete the feature's copy and edit \`glossary/<term>.md\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
|
|
216
|
+
| \`usecase.flow-exists\` | a \`features/<FEAT>/usecases/<name>.likec4\` whose flow the LIVING \`architecture/usecases/\` already holds. The merge is a whole-file copy, so it would replace an authored hop sequence wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because a \`dynamic view\`'s ordered steps are written down in no other document the fleet keeps. A feature-local flow INTRODUCES a use case; it does not rewrite one | delete the feature's copy and edit \`architecture/usecases/<name>.likec4\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
|
|
217
|
+
| \`deployment.doc-exists\` | a \`features/<FEAT>/deployment/<name>.likec4\` whose file the LIVING \`architecture/\` already holds. The merge is a whole-file copy, so it would replace an authored topology wholesale, and \`--approve\` does NOT move it — the same act, and the same severity, as \`usecase.flow-exists\` one axis over. The collision is on the FILE, never on what is inside it: two features may both \`extend\` one living region from documents of their own and both archive | delete the feature's copy and edit \`architecture/<name>.likec4\` directly in the same change — or, to ADD to what that file declares, rename the feature's document and say \`extend\` in it |
|
|
218
|
+
| \`deployment.doc-invalid\` | a \`features/<FEAT>/deployment/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The corpus is the feature's \`deployment/\` PLUS the living \`architecture/\` documents it \`extend\`s, so the fault may be in either and every message names its file. \`--approve\` does not move it: the flag overrides loam's judgement about coherence, never its ability to read an axis | fix what the messages name. A document instancing an element only this feature's \`delta.likec4\` adds is LEGAL and resolves — that is what the merge preview is for; one naming an element the merge does not land is the case this refuses |
|
|
219
|
+
| \`deployment.doc-reserved\` | a \`features/<FEAT>/deployment/<name>.likec4\` whose name loam owns inside \`architecture/\`: \`subsystems.likec4\` and \`services.likec4\` (both generated, overwritten wholesale by \`loam subsystem sync\`, and parsed by nothing — so topology landing in either is invisible from the moment it archives), \`landscape.likec4\` (spliced into, never replaced), or any path under \`usecases/\` (the flow tree). Refused whether or not the target exists, which is the point: three of the four fail silently when it does not | rename the feature's document — its name is yours to choose, and \`extend\` is how it adds to what a living document already declares |
|
|
211
220
|
| \`delta.unknown-section\` | a heading that nearly matches the delta grammar | fix it — everything under it merges as NOTHING today, silently |
|
|
212
221
|
| \`delta.no-delta-sections\` | requirements, but no \`## ADDED/MODIFIED/REMOVED Requirements\` section anywhere — the whole delta would merge nothing | put every changed requirement under its delta section |
|
|
213
222
|
| \`delta.requirement-not-merged\` (warn, gates archive) | a requirement under a prose heading (\`## Behavior\`) instead of a delta section | move it under \`## ADDED\`/\`## MODIFIED\`/\`## REMOVED Requirements\` — as written, archive drops it. If it really is documentation, quote it under \`## Requirements\`, which is exempt |
|
|
@@ -232,30 +241,35 @@ what lets a feature ship:
|
|
|
232
241
|
| code | what it means | what to do |
|
|
233
242
|
|---|---|---|
|
|
234
243
|
| \`landscape.missing\` | \`architecture/landscape.likec4\` does not exist — an ERROR as soon as \`services/\` holds one service, a warning only in an empty docs repo | create it: a \`specification { ... }\` block declaring the kinds, then a \`model { ... }\` block with one bound element per service (\`metadata { service '<id>' }\`) and one edge per cross-service call carrying \`metadata { op '<operationId>' }\`. With no fleet map every cross-service check is blind, not passing |
|
|
235
|
-
| \`landscape.invalid\` | the living landscape does not parse | fix it first — the fleet cross-check cannot run against a document nobody can read |
|
|
244
|
+
| \`landscape.invalid\` | the living landscape does not parse. The fleet map is graded as the \`architecture/\` project and NOTHING ELSE — the landscape plus every \`architecture/**/*.likec4\` except the generated views files (\`subsystems.likec4\` and \`services.likec4\`) — whatever shape the service models take. That is deliberate: the renderer's root project is wider (it also holds every extending model), so a hop in \`architecture/usecases/\` naming a CONTAINER an extending model declares renders perfectly and is \`landscape.invalid\` here. A cross-service flow names service elements; a container-level flow lives beside the model that declares the container. One broken service model must never blank the fleet's own map | fix it first — the fleet cross-check cannot run against a document nobody can read. If the unresolved name is a container an extending model declares, the reference belongs one altitude down whatever carries it: a use-case hop moves beside that service's model.likec4 and is tagged \`#req-\`; a model edge names the SERVICE on the map and the container in the service's own model |
|
|
236
245
|
| \`service.id-invalid\` | a \`services/<id>/\` directory whose name is not a legal service id — every authoring command refuses it, so nothing in that directory can be changed through loam. Fleet scope only: the SET of service directories is what makes the id a question, and a \`--service\` run is already refused by its own guard before it gets this far (the guard refuses only a name that neither exists as a directory nor is a legal id — a badly-named directory that DOES exist resolves through the enumeration and grades as an ordinary target, still without this finding). Graded before the map is opened, so it stands even when the landscape is missing or unreadable | rename the directory, then update its \`service:\` frontmatter, its \`metadata { service }\` binding in the landscape, and any \`features/<FEAT>/specs/<id>/\` naming it |
|
|
237
246
|
| \`subsystem.unmarked\` | a directory under \`services/\` that is neither a subsystem (no \`subsystem.yaml\`) nor a service (no service artifact), with directories beneath it — the shape an ordinary merge produces when one branch deletes an emptied subsystem's marker while another moves a service in. The services beneath STAY enumerated and the finding names every one of them: the fleet is never reported smaller than it is | restore \`subsystem.yaml\` in that directory (an empty file is a valid marker), or move the directories out |
|
|
238
247
|
| \`subsystem.marker-misplaced\` | \`subsystem.yaml\` sitting BESIDE service artifacts (spec.md, model.likec4, …) — a directory is a subsystem or a service, never both; it stays classified as a service | delete the marker, or move the artifacts into a service directory of their own inside the group |
|
|
239
248
|
| \`subsystem.name-collision\` | one name claimed by two or more directories in the \`services/\` tree — service ids and subsystem names share one flat namespace, unique at any depth, because a service's identity is its leaf directory name alone and placement never disambiguates it. The message names every claimant | rename all but one, then update whichever frontmatter, landscape bindings and \`features/<FEAT>/specs/<id>/\` name the renamed service |
|
|
240
249
|
| \`subsystem.name-invalid\` | a subsystem directory name that breaks the id grammar (a space, a \`..\`, a Windows-reserved stem, a trailing dot) — subsystem names take the service-id grammar because both become directory names in one shared tree. Its services stay enumerated meanwhile | rename the directory to a legal name |
|
|
241
250
|
| \`subsystem.invalid\` | a \`subsystem.yaml\` that exists but cannot be read as subsystem metadata — presence still classifies the directory as a subsystem (content is metadata, never identity), and exactly one finding is emitted per file | fix the file: a YAML mapping with optional \`title\`, \`description\` and \`owner\`; never \`members\` — the directory itself is the membership record. An empty file is also a valid marker |
|
|
242
|
-
| \`subsystem.views-stale\` | \`architecture/subsystems.likec4\`
|
|
243
|
-
| \`service.likec4-config-missing\` (warn) | a
|
|
244
|
-
| \`
|
|
251
|
+
| \`subsystem.views-stale\` | a GENERATED views file under \`architecture/\` does not match the tree and the map. There are TWO, written by the same command in the same commit, and each earns its own finding naming its own path: \`architecture/subsystems.likec4\` (one view per subsystem) and \`architecture/services.likec4\` (one overview view per service the fleet map draws — the service, its own interior, every peer the map gives it, and \`of <element>\`, which is what makes every other view drill into it). A file is stale when it drifted after a move, is absent while the thing it mirrors exists, lingers after the last of them was removed, or the map moved under it — a service added, an edge drawn, a subsystem renamed, or the global style groups changed. Styling restales the subsystem file twice over: declaring or removing a group named \`subsystems\` changes whether its views carry the \`global style subsystems\` line, and declaring a group under ANY other name changes the disclosure comment that names what is available; the per-service file reads a group named \`services\` the same way. The census reads every \`.likec4\` under \`architecture/\` the root \`likec4.config.json\` does not exclude, so a palette in a sibling counts. One error per stale file, graded by content comparison against the deterministic re-render — never by reading what a view SHOWS (docs/DESIGN.md rule 26) | run \`loam subsystem sync\` — the single regenerator for both files. Never edit either by hand; authored views belong in the landscape or a file of your own, and an authored view \`of\` the same element keeps precedence whenever its file sorts first (\`landscape.likec4\` does, ahead of \`services.likec4\`), with both still reachable. \`sync\` refuses to rewrite a generated file while \`architecture/\` does not parse (\`action: "blocked"\`) — fix the map first; \`validate\` already skips the staleness question in that state. A docs repo that has not run \`sync\` since \`architecture/services.likec4\` existed sees this once for that file; one run clears it |
|
|
252
|
+
| \`service.likec4-config-missing\` (warn) | a model that STANDS ALONE — it declares its own \`specification\` block, so it is parsed alone and cannot share a project with the map — with no \`likec4.config.json\` beside it. It renders only as a project of its own and this one has none: a box on the fleet map with nothing renderable inside it. One warning per service, on the fleet target, only when the root \`likec4.config.json\` exists (\`loam doctor\` prints that one); never gating, because no loam check reads the model any differently. A model that EXTENDS the map never earns this — it belongs to the root project | run \`loam subsystem sync\` — it creates the file (create-only: a file that already exists is yours and is never touched). Never hand-write it; the bytes are a function of the service id, and the writer spells them once |
|
|
253
|
+
| \`service.likec4-config-stray\` (warn) | the mirror: a \`likec4.config.json\` beside a model that EXTENDS the fleet map. The renderer registers that file as a project of its own rooted at that directory, so \`likec4 validate .\` at the docs root refuses without \`--project\`, and wherever that nested project claims the model it parses alone, out of the root project where the map's kinds are: the model does not resolve and \`export json --project fleet\` loses the service's containers (measured, likec4 1.59.2 — it happened on a seeded fleet and not on \`examples/docs\`). Fleet target, root-gated like its siblings, never gating | run \`loam subsystem sync\` — it deletes the file (or delete it yourself). loam never writes one beside an extending model, so this one is a leftover from before the model migrated, or was hand-written |
|
|
254
|
+
| \`service.model-excluded\` (warn) | a model that EXTENDS the fleet map whose directory the root \`likec4.config.json\`'s \`exclude\` covers — or whose \`model.likec4\` a FILE-shaped entry (\`services/**/model.likec4\`) names; the message says which shape it found. An entry is read the way the renderer reads it: a bare entry is a raw path PREFIX from a \`/\` boundary, so \`services\`, \`services/\`, \`services/*\`, \`services/**\`, \`services/**/*\`, a wildcard segment in the middle (\`services/*/legacy\`) and the bare \`services/pay\` all cover \`services/payment-service\` — while \`services/pay/**\` wants the literal separator and covers nothing there. The renderer never loads the model, so the service is a box with nothing inside it from the docs root, while every loam check still grades it perfectly: the gap is invisible except here. The finding quotes the entry verbatim, because the repair is to edit that line | for a DIRECTORY entry, run \`loam subsystem sync\` — it rewrites the root project's \`exclude\` to cover exactly the models that stand alone, keeping every entry that is not about \`services/\` in the order the team wrote it. For a FILE-shaped entry sync repairs nothing: remove or narrow that line by hand, then re-run |
|
|
255
|
+
| \`service.model-unexcluded\` (warn) | the mirror of that one: a model that STANDS ALONE whose directory the root \`exclude\` does NOT cover. The renderer merges it into the root project and reports every kind and element it declares as a duplicate of the map's — which blanks the whole root project, not just this service. The most damaging of the four, and the one a fresh scaffold walks into the moment somebody writes a model in the old shape. An entry already covering the directory in any of its spellings satisfies it — no second glob is wanted, and none is appended | run \`loam subsystem sync\` — it adds \`services/<tree>/**\` to the root project's \`exclude\`. Or migrate the model to extend the map (SCHEMA.md, "Two shapes of a service model"), after which no exclusion is wanted |
|
|
256
|
+
| \`landscape.excluded\` (warn) | the same root \`exclude\` list, one level up: an entry covering \`architecture/landscape.likec4\` itself. The renderer then loads NO fleet map — it draws nothing, and every model that EXTENDS the map resolves against nothing in the root project, which is also what WOULD turn \`c4.fleet-project-invalid\` into a page of unresolved references — measured on the packaged example fleet, one authored line came back as 161 of them. So that check is not graded at all while this entry stands, and this finding is the only line in the run that names the cause; remove the entry and the fleet project is graded again. loam is otherwise unaffected: the architecture loader keeps the map whatever the list says, so every grade in the run is right, and that is exactly why the state was invisible until this code existed. An entry is matched as a FILE path here, not as a directory, so \`architecture/*.likec4\` — a line somebody wrote for a palette — covers it while \`services/**\` and \`features/**\` do not. Quoted verbatim, because the repair is to edit that line | remove the entry, or narrow it to the paths it was written for. NOT \`loam subsystem sync\`: that command maintains only the \`services/\` entries in the list and leaves every other line exactly as the team wrote it, so it would report success and change nothing |
|
|
257
|
+
| \`c4.fleet-project-invalid\` (warn) | every document reads clean where loam grades it, and the ONE project the renderer builds out of them does not parse. \`--all\` only, only when at least one extending model exists, the map itself parses, and the root \`exclude\` does not cover the map (\`landscape.excluded\` then names that one entry and this check stands down behind it, because this load reads what the renderer reads and has no floor under the map): loam walks the docs root and loads what the renderer's root project loads — everything the root \`exclude\` leaves, minus the generated views files (\`subsystems.likec4\` and \`services.likec4\`), minus every STANDALONE service directory (\`service.model-unexcluded\` owns that state), minus \`features/\` (a delta carries its own \`specification\` and the directory is transient) — and reports each error that is NOT already present in the map alone, in some per-service project, or in a service-flow project the same run made. A \`.likec4\` in a SUBSYSTEM directory, above every service, is in that set. That difference is exactly the class no per-service grade can see: two services each declaring \`tag req-AUTH\` locally, two models adding the same child id under one element, a broken untagged sibling nobody opted in. All of them are renderer-fatal and all of them were silent. \`locations[0]\` is the file the error is IN, \`details\` carry \`<path>: L<n>: <error>\` 1-based — path, colon, space, then the line, exactly as the message's own head spells it, so an agent splitting on the separator gets the same two halves from either — and the message tail names the failure it actually is — a double declaration, an unresolved reference, or neither. \`subject\` is the service whose directory holds the file, where there is one | declare it once — in \`architecture/landscape.likec4\`, or in the single service that owns it — at the file and line the message names. Never by excluding the directory: that hides the model from the renderer instead (\`service.model-excluded\`) |
|
|
258
|
+
| \`subsystem.view-id-collision\` | a document the root project reads declares a view whose id is one loam GENERATES — \`subsystem_<name>\` into \`architecture/subsystems.likec4\`, or \`service_<id>\` into \`architecture/services.likec4\`. Two reserved prefixes, one code, because the failure is identical: LikeC4 merges every file of a project into one model and view ids are a flat namespace there, so the renderer refuses the WHOLE project and draws nothing — while every other check here stays green. Under \`--all\` the census covers every EXTENDING model's authored view ids as well as the landscape's, because those documents are in the same root project now; a standalone model's are not, since its views live in a project of its own. Only a real collision is reported: a \`subsystem_\`-looking id matching no current subsystem breaks nothing yet and fires on the run after somebody creates it, and a \`service_\`-looking id is reserved only for a service the fleet MAP DRAWS — an unmodelled \`services/<id>/\` mints nothing until an element resolves to it | rename the view in the file the finding names — the message names which file and which prefix. Ids beginning \`subsystem_\` or \`service_\` are loam's to mint; both generated files are written by \`loam subsystem sync\` and are never hand-edited |
|
|
245
259
|
| \`landscape.merge-conflict\` | the fleet map still holds conflict markers — it is two halves of two different maps, and every cross-service check reads it. Checked BEFORE the parse and returns like \`landscape.invalid\`: nothing is concluded from the file, so no \`landscape.service-unmodelled\` fires behind it | resolve it keeping BOTH sides' elements and edges; ten people adopting ten services into one landscape.likec4 in the same week is how it happens. \`loam archive\` refuses on it too (\`merge-failed\`, not overridable with \`--approve\`) |
|
|
246
260
|
| \`landscape.service-unmodelled\` | a \`services/<svc>/\` no element in the landscape resolves to | draw it, or bind an existing element with \`metadata { service '<svc>' }\` — the fleet map is incomplete until you do |
|
|
247
261
|
| \`landscape.service-undocumented\` (warn) | a landscape element with no \`services/<id>/\` | document the service, bind the element to the directory it means, or tag it \`#external\` if it is not ours |
|
|
248
262
|
| \`landscape.binding-unknown\` | an element's \`metadata { service }\` names a directory that does not exist | fix the id or create the service — a binding is a claim, and this one is false |
|
|
249
263
|
| \`landscape.binding-duplicate\` (warn) | two elements resolve to the same \`services/<id>/\` | every element→service join picks one of them arbitrarily, so the other's edges are filed under a service that does not own them — bind each element to the directory it actually is, or tag the stray one \`#external\` |
|
|
264
|
+
| \`landscape.service-isolated\` (warn) | an element resolves to a \`services/<id>/\` and no edge anywhere in \`architecture/\` touches it, while that service's own \`model.likec4\` declares a call across its boundary. The map draws the service and nothing reaches it, so every cross-service check is vacuous over it — and \`loam validate --service\` cannot see it. Evidence-gated: a service whose model declares no cross-boundary call is silent here, and the adopt brief's \`landscape.touched\` is the only place that state is named. Binary, too: ONE edge closes it, so a map drawing one of five attested calls is silent — the check is touched/untouched, not a set difference, and the calls left undrawn are named nowhere. An \`#external\` element is never the subject, whatever it is bound to; the touched side applies no tag filter — an edge on an \`#external\` element the service owns (a kind-tagged topic, say) still touches it, exactly as the adopt brief's \`landscape.touched\` counts it | draw each attested call as one edge on the element, collapsed to the service, carrying \`metadata { op '<operationId>' }\` for a call or \`publishes\`/\`consumes\` for an event, naming the other party as the map already spells it — never a second element |
|
|
250
265
|
| \`landscape.kind-tag-graded\` | the \`specification\` block declares \`#external\` or \`#platform\` on an element KIND, and at least one element of that kind stands for a real \`services/<id>/\` — bound with \`metadata { service }\`, or titled like one. Since LikeC4 1.59.0 a kind's tags land on every element of it, and loam reads both tags as "not ours, stop grading", so the exemption is silencing the checks for a service of yours; the run would otherwise end on \`landscape.matched\` over a map nothing checked | remove the tag from the kind and write \`#external\` / \`#platform\` on the individual elements that really are foreign — or stop drawing your own services as that kind. A graded tag on a kind whose elements stand for no directory of yours (the \`element topic { #external }\` idiom for the topics inside a broker) is correct and stays silent, and any OTHER tag is safe on any kind — loam does not read it |
|
|
251
266
|
| \`landscape.platform-candidate\` (warn) | an \`#external\` element consumed by three or more services and not tagged \`#platform\` — a hub on its way to making the fleet view unreadable | declare \`tag platform\` in the \`specification\` block and tag the element (LikeC4 refuses an undeclared tag, so both steps are needed): the fleet view then excludes it, and a platform view over \`include * -> element.tag = #platform\` keeps "who depends on it" answerable |
|
|
252
|
-
| \`landscape.datastore-
|
|
253
|
-
| \`
|
|
254
|
-
| \`usecase.capability-unresolved\` | a \`dynamic view\` tagged \`#cap-<slug>\` whose slug matches no declared capability id, or more than one. The tag spells the id with every character outside \`[A-Za-z0-9_-]\` flattened to \`-\` (that is all a LikeC4 tag name accepts) and that flattening is not injective, so \`payments/refunds\` and \`payments-refunds\` both answer to one tag. Silent while \`architecture/capabilities.yaml\` is absent or invalid, like the rest of that family — the FILE is the capability axis's opt-in | zero matches: fix the tag, or declare the capability in the fleet vocabulary (the message offers close ids, each already spelled as the tag you would write). More than one: rename one of the colliding ids — nothing in the tag can say which the flow realizes, and a guess files every hop of it under an owner who never claimed it |
|
|
267
|
+
| \`landscape.datastore-shared\` (warn) | a datastore consumed by two or more services — the strongest coupling in the fleet, stated rather than inferred. Two shapes: a store drawn as a fleet-level peer, and a store NESTED inside its owner's element that another service reaches — the message adds \` — <owner>'s own store, written as '<store-fqn>'\` for the second, \`<store-fqn>\` being the store's own id, and then says the move is ONE-WAY at this point: the other consumers' edges resolve only against a declaration the MAP holds, so out of the owner's \`extend\` block onto the map is always safe while moving it back INTO the \`extend\` block breaks them (\`c4.invalid\` on that service's model, or \`landscape.invalid\` when the edge is drawn on the map). Consumers are counted from the map's edges AND from every extending model's attested outbound calls | shared means the same DATA: if they read the same tables or keys, keep it drawn and let the warning state the coupling; if they only share a host or cluster (two schemas, two lock paths), model one private store per service and put the blast radius in the runbook |
|
|
268
|
+
| \`usecase.capability-unresolved\` | a \`dynamic view\` tagged \`#cap-<slug>\` whose slug matches no declared capability id, or more than one. The tag spells the id with every character outside \`[A-Za-z0-9_-]\` flattened to \`-\` (that is all a LikeC4 tag name accepts) and that flattening is not injective, so \`payments/refunds\` and \`payments-refunds\` both answer to one tag. Silent while \`architecture/capabilities.yaml\` is absent or invalid, like the rest of that family — the FILE is the capability axis's opt-in, or carried by a flow inside a service's own project (a \`dynamic view\` beside model.likec4), where no capability can be claimed — that arm fires whether or not the vocabulary exists, and the fix is to drop the tag or move the flow to \`architecture/usecases/\` | zero matches: fix the tag, or declare the capability in the fleet vocabulary (the message offers close ids, each already spelled as the tag you would write). More than one: rename one of the colliding ids — nothing in the tag can say which the flow realizes, and a guess files every hop of it under an owner who never claimed it |
|
|
255
269
|
| \`usecase.requirement-unresolved\` | a \`dynamic view\` tagged \`#req-<slug>\` whose slug names no requirement of the capability that view is about. Six failures with six fixes, and the message says which: the view resolves no \`#cap-\` tag (a \`Requirement-ID\` is unique only inside its own document, so the id has no scope), it resolves two (nothing says which document), the capability has no \`capabilities/<id>/spec.md\`, that document declares no requirements yet, it declares none flattening to this slug, or two of its ids flatten to one slug. It suppresses NOTHING: only a RESOLVED claim counts as a promise kept, so a broken tag leaves \`capability.requirement-unrealized\` firing beside it | follow the message. Several \`#req-\` tags on one view are legal and normal — a flow commonly keeps two of a capability's promises |
|
|
256
270
|
| \`usecase.step-unbacked\` | a hop of a capability-tagged \`dynamic view\` that NO relationship in the model backs, on either tier. LikeC4 reports no error for it — a dynamic view is allowed to show a call the model never declared — so the diagram renders, the project parses, and every other check stays green. The finding names the file the view was written in, the step's ordinal and title, and the oriented pair | draw the edge in \`model { }\` (with \`metadata { op '<operationId>' }\` where it is a call), or, if the hop is a return step, write it as \`<target> <- <source>\`: a reply is attributed to the CALL it answers, so the \`<-\` spelling looks the hop up under the reversed pair instead |
|
|
257
271
|
| \`usecase.step-contested\` (warn) | two or more relationships back one hop and they name DIFFERENT operations — an absent \`metadata { op }\` counts as a value there, not as a gap — so nothing can say which one the hop exercises. \`details\` lists every candidate as \`<source> -> <target> "<title>" (op: <id>)\` | give the candidates one operation, or draw the hop between the elements that name exactly one. loam names them rather than picking, because a guessed operation reads exactly like a right one in every rollup built over the flow. It suppresses \`usecase.step-unlinked\` on the same hop: one breach, one finding |
|
|
258
|
-
| \`usecase.step-unlinked\` (warn) | the relationship backing a hop carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's CALLER is not a person and its target resolves to a real \`services/<id>/\` — the flow reaches the fleet map and stops short of the contract it drives. \`spine.op-link-missing\` at use-case altitude. A hop FROM a person never fires it — the first hop of almost every sequence diagram is somebody using the app, and an app owes no operationId for a click — and neither does a hop INTO an \`#external\` system, an actor, a database, a cache or a queue: none of those owns a contract to name. A \`publishes\`/\`consumes\` edge already reaches an API — an AsyncAPI one | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
|
|
272
|
+
| \`usecase.step-unlinked\` (warn) | the relationship backing a hop carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's CALLER is not a person and its target resolves to a real \`services/<id>/\` — the flow reaches the fleet map and stops short of the contract it drives. \`spine.op-link-missing\` at use-case altitude. A hop FROM a person never fires it — the first hop of almost every sequence diagram is somebody using the app, and an app owes no operationId for a click — and neither does a hop INTO an \`#external\` system, an actor, a database, a cache or a queue: none of those owns a contract to name. A \`publishes\`/\`consumes\` edge already reaches an API — an AsyncAPI one. Never on a hop whose caller and provider resolve to the SAME service — two containers of one service, inside its own project or drawn on the landscape — because a service owes no operationId to itself; that is the fourth mechanical guard, and its whole scope. A hop from a service-local flow into ANOTHER service's element (a stand-in model.likec4 declares for a sibling, with no \`metadata { op }\`) still warns, on the service target, exactly as it does on the fleet map | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
|
|
259
273
|
| \`feature.unreadable\` | an artifact of a feature in flight could not be read — nothing about that feature was graded, and the rest of the fleet still was | fix the file the finding names; a feature silently skipped would have read as a clean one |
|
|
260
274
|
| \`sources.unverifiable-from-here\` (ok) | one finding PER SERVICE whose \`sources\` only its own repo can check — a confirmation, not work, so it never trips \`--strict\`, and it is present in \`--service\` runs from the docs repo too | run \`loam validate --service <id>\` from inside that service's repo to actually grade its provenance; the fleet rollup line is derived from these findings |
|
|
261
275
|
| \`docs.binary-behind\` (warn) | the docs repo's AGENTS.md stamp names a loam NEWER than the one running. This is the one finding that changes what a PASS means: a binary predating a grammar addition does not fail on the newer directive, it reads it as prose, has no join to fail, and reports green — so the corpus was checked less than the run implies. Warn rather than error because a mixed-version fleet is ordinary; \`--strict\` is the lever if it should gate | upgrade loam and re-run. Do NOT edit AGENTS.md — its stamp is correct and this binary is the older half |
|
|
@@ -320,7 +334,7 @@ always carries them):
|
|
|
320
334
|
|
|
321
335
|
| code | what it means | what to do |
|
|
322
336
|
|---|---|---|
|
|
323
|
-
| \`c4.valid\` (ok) | the service's model.likec4 parses and the C4 axis was actually graded | nothing — it is the confirmation |
|
|
337
|
+
| \`c4.valid\` (ok) | the service's model.likec4 parses and the C4 axis was actually graded. Its message says HOW the file was read: "extends the fleet map — N elements · M relationships" for a model with no \`specification\` block of its own, parsed together with \`architecture/landscape.likec4\`; the plain wording for one that stands alone. A trailing \` · K tagged flow(s) graded\` says the service's own project declared use cases and they were graded — the one surface a HEALTHY intra-service flow appears on | nothing — it is the confirmation |
|
|
324
338
|
| \`delta.valid\` (ok) | the feature's delta.likec4 parses and its tagged change is well-formed | nothing — it is the confirmation |
|
|
325
339
|
| \`coherence.ok\` (ok) | the feature's axes agree with each other and with the fleet in flight — nothing gates its archive | nothing — it is the confirmation |
|
|
326
340
|
| \`requirements.covered\` (ok) | every requirement carries at least one scenario | nothing — it is the confirmation |
|