@ybotok/loam 0.2.0-alpha.6 → 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.
Files changed (105) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/README.md +4 -4
  3. package/ROADMAP.md +27 -6
  4. package/SCHEMA.md +152 -32
  5. package/dist/commands/archive/run.js +49 -22
  6. package/dist/commands/archive/run.js.map +1 -1
  7. package/dist/commands/seed/commit.js +34 -1
  8. package/dist/commands/seed/commit.js.map +1 -1
  9. package/dist/commands/seed/plan.js +64 -13
  10. package/dist/commands/seed/plan.js.map +1 -1
  11. package/dist/commands/subsystem/edit.js +4 -2
  12. package/dist/commands/subsystem/edit.js.map +1 -1
  13. package/dist/commands/subsystem/move.js +55 -17
  14. package/dist/commands/subsystem/move.js.map +1 -1
  15. package/dist/commands/subsystem/sync.js +142 -45
  16. package/dist/commands/subsystem/sync.js.map +1 -1
  17. package/dist/commands/subsystem/txn/report.js +55 -5
  18. package/dist/commands/subsystem/txn/report.js.map +1 -1
  19. package/dist/commands/subsystem/txn/txn.js +118 -21
  20. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  21. package/dist/commands/subsystem/txn/views.js +51 -8
  22. package/dist/commands/subsystem/txn/views.js.map +1 -1
  23. package/dist/commands/validate/checks/health-deps.js +9 -6
  24. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  25. package/dist/commands/validate/fleet/census.js +26 -3
  26. package/dist/commands/validate/fleet/census.js.map +1 -1
  27. package/dist/commands/validate/fleet/landscape.js +12 -9
  28. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  29. package/dist/commands/validate/fleet/map/attest.js +4 -5
  30. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  31. package/dist/commands/validate/fleet/map/bindings.js +15 -2
  32. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  33. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  34. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  35. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  36. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  37. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  38. package/dist/commands/validate/fleet/views/ids.js +33 -7
  39. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  40. package/dist/commands/validate/fleet/views/root-documents.js +9 -7
  41. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  42. package/dist/commands/validate/fleet/views/stale.js +31 -14
  43. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  44. package/dist/core/agent/agents-md/command-map.js +1 -1
  45. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  46. package/dist/core/agent/agents-md/map/subsystem.js +27 -14
  47. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  48. package/dist/core/agent/agents-md/refusals.js +5 -3
  49. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  50. package/dist/core/agent/agents-md.js +16 -7
  51. package/dist/core/agent/agents-md.js.map +1 -1
  52. package/dist/core/agent/workflows/check.js +5 -6
  53. package/dist/core/agent/workflows/check.js.map +1 -1
  54. package/dist/core/agent/workflows/reference/spine.js +12 -6
  55. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  56. package/dist/core/brief/brief.js +25 -8
  57. package/dist/core/brief/brief.js.map +1 -1
  58. package/dist/core/brief/targets.js +64 -0
  59. package/dist/core/brief/targets.js.map +1 -1
  60. package/dist/core/brief/unchecked.js +1 -1
  61. package/dist/core/brief/unchecked.js.map +1 -1
  62. package/dist/core/c4/project/architecture.js +31 -9
  63. package/dist/core/c4/project/architecture.js.map +1 -1
  64. package/dist/core/c4/project/documents.js +5 -4
  65. package/dist/core/c4/project/documents.js.map +1 -1
  66. package/dist/core/c4/project/load.js +8 -4
  67. package/dist/core/c4/project/load.js.map +1 -1
  68. package/dist/core/c4/project/staged.js +7 -3
  69. package/dist/core/c4/project/staged.js.map +1 -1
  70. package/dist/core/deployment/delta.js +12 -10
  71. package/dist/core/deployment/delta.js.map +1 -1
  72. package/dist/core/gate/checks.js +5 -1
  73. package/dist/core/gate/checks.js.map +1 -1
  74. package/dist/core/gate/partners.js +60 -17
  75. package/dist/core/gate/partners.js.map +1 -1
  76. package/dist/core/repo/paths.js +34 -29
  77. package/dist/core/repo/paths.js.map +1 -1
  78. package/dist/core/repo/tree/render/body.js +45 -6
  79. package/dist/core/repo/tree/render/body.js.map +1 -1
  80. package/dist/core/repo/tree/render/service/header.js +125 -0
  81. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  82. package/dist/core/repo/tree/render/service/peers.js +28 -0
  83. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  84. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  85. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  86. package/dist/core/repo/tree/render/service/views.js +78 -0
  87. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  88. package/dist/core/repo/tree/render/stale.js +19 -2
  89. package/dist/core/repo/tree/render/stale.js.map +1 -1
  90. package/dist/core/repo/tree/render/views.js +102 -33
  91. package/dist/core/repo/tree/render/views.js.map +1 -1
  92. package/dist/core/verify/checklist.js +42 -10
  93. package/dist/core/verify/checklist.js.map +1 -1
  94. package/dist/core/vocabulary/issue.js.map +1 -1
  95. package/dist/core/vocabulary/maturity.js +41 -1
  96. package/dist/core/vocabulary/maturity.js.map +1 -1
  97. package/examples/README.md +7 -3
  98. package/examples/docs/architecture/landscape.likec4 +13 -7
  99. package/examples/docs/architecture/services.likec4 +70 -0
  100. package/examples/docs/architecture/subsystems.likec4 +3 -0
  101. package/examples/docs/services/order-service/model.likec4 +10 -8
  102. package/examples/docs/services/payment-service/model.likec4 +6 -4
  103. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  104. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  105. package/package.json +1 -1
@@ -216,7 +216,7 @@ what lets a feature ship:
216
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
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
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\` (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 |
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 |
220
220
  | \`delta.unknown-section\` | a heading that nearly matches the delta grammar | fix it — everything under it merges as NOTHING today, silently |
221
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 |
222
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 |
@@ -241,21 +241,21 @@ what lets a feature ship:
241
241
  | code | what it means | what to do |
242
242
  |---|---|---|
243
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 |
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 \`subsystems.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 |
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 |
245
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 |
246
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 |
247
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 |
248
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 |
249
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 |
250
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 |
251
- | \`subsystem.views-stale\` | \`architecture/subsystems.likec4\` — the GENERATED file holding one LikeC4 view per subsystem — does not match the tree: it drifted after a move, it is absent while subsystems exist, it lingers after the last subsystem was removed, or the map's global style groups changed. Styling restales it twice over: declaring or removing a group named \`subsystems\` changes whether the generated views carry the \`global style subsystems\` line, and declaring a group under ANY other name changes the disclosure comment that names what is available — either way the deterministic re-render differs from the bytes on disk. The census reads every \`.likec4\` under \`architecture/\` the root \`likec4.config.json\` does not exclude, so a palette in a sibling counts. One error on one file, graded by byte comparison against that re-render (no check ever reads the file's content) | run \`loam subsystem sync\` — the single regenerator. Never edit the file by hand; authored views belong in the landscape or a file of your own. \`sync\` refuses to rewrite this file while \`architecture/\` does not parse (\`action: "blocked"\`) — fix the map first; \`validate\` already skips the staleness question in that state |
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
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
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
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
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
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 \`subsystems.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 into \`architecture/subsystems.likec4\`. 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 that subsystem | rename the view in the file the finding names. Ids beginning \`subsystem_\` are loam's to mint; \`architecture/subsystems.likec4\` is generated (\`loam subsystem sync\`) and is never hand-edited |
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 |
259
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\`) |
260
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 |
261
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 |
@@ -264,7 +264,6 @@ what lets a feature ship:
264
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 |
265
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 |
266
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 |
267
- | \`landscape.datastore-private\` (warn) | a datastore element with exactly one consumer service at fleet level — drawn as a peer, it reads as a system in its own right. Consumers are counted from the map's edges AND from every EXTENDING model's attested outbound calls, so a store whose consuming edges all live in models is graded rather than silent | it is that service's internals: write it inside the element that resolves to that service — the \`extend <fqn> { }\` block of \`services/<id>/model.likec4\` when that model EXTENDS the map, or inside \`<fqn>\` on the map when the map draws that service's containers — and delete the peer. The message names whichever the consumer's model shape allows: a model that declares its own \`specification\` is parsed alone, so \`extend <fqn>\` resolves nothing there and the map is the only placement (migrate the model to the extending shape, or write one when there is none). Either way the two placements give the store the same id, \`<fqn>.<store>\`, and with one consumer either move is free — out of the \`extend\` block onto the map always is, and back into it while no other service's model or map edge names the store, an \`extend\` block being visible to its own service's project alone. Nesting closes no door: loam counts a consumer an EXTENDING model attests, so a second service reaching the same data is \`landscape.datastore-shared\` rather than silence — a consumer whose model stands alone is not counted, its ids being its own file's, so its edge belongs on the map. That second edge resolves only against a store the MAP declares (a per-service project is \`architecture/\` plus one model), so nest it on the map when a second consumer is foreseeable. If another service reaches the same data today, draw its edge and this becomes that warning now |
268
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 |
269
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 |
270
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 |