@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.4
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 +165 -0
- package/README.md +6 -6
- package/ROADMAP.md +155 -18
- package/SCHEMA.md +33 -1
- package/dist/commands/archive/plan/gate.js +18 -12
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +19 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/run.js +48 -3
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/seed/plan.js +1 -1
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/subsystem/sync.js +1 -1
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +1 -1
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +23 -11
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +11 -1
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +43 -9
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/steps.js +3 -3
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
- package/dist/commands/validate/fleet/views/ids.js +6 -1
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/stale.js +15 -39
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/service/service.js +1 -0
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +33 -1
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +8 -1
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md.js +4 -0
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/workflows/check.js +3 -0
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/brief/unchecked.js +1 -0
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +72 -180
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/likec4.js +2 -0
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/project/load.js +4 -0
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +46 -0
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +15 -2
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/workspace.js +2 -0
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/coherence/coherence.js +36 -0
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/pack/living.js +19 -0
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/repo/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/paths.js +44 -3
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +219 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +26 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/{views.js → render/views.js} +38 -34
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +29 -4
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +11 -1
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/usecases/delta/overlay.js +28 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +2 -2
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/vocabulary/issue.js +3 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/examples/docs/architecture/subsystems.likec4 +4 -0
- package/package.json +1 -1
- package/dist/core/repo/tree/views.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,171 @@ case for a change — the alternative that was rejected, the defect it came from
|
|
|
10
10
|
generalises — lives where it is maintained: [SCHEMA.md](SCHEMA.md) for the rule,
|
|
11
11
|
[ROADMAP.md](ROADMAP.md) for the priority and its exit criteria, and the commit that landed it._
|
|
12
12
|
|
|
13
|
+
## [0.2.0-alpha.4] - 2026-09-03
|
|
14
|
+
|
|
15
|
+
The view title becomes the subsystem's path. `0.2.0-alpha.3` was tagged and **never published** — it
|
|
16
|
+
was withdrawn before its publish step for a reason unrelated to the code — so this is the release
|
|
17
|
+
that carries both it and the entry below, and `npm i @ybotok/loam@alpha` moves from `0.2.0-alpha.2`
|
|
18
|
+
straight to this one. A repo upgrading from either reports one `subsystem.views-stale` until one
|
|
19
|
+
`loam subsystem sync`.
|
|
20
|
+
|
|
21
|
+
### Changed — a generated subsystem view is titled by its path
|
|
22
|
+
|
|
23
|
+
`0.2.0-alpha.3` gave each view the subsystem's own label. It now carries the whole path — every
|
|
24
|
+
marked ancestor's label and its own, joined with ` / `, so `services/platform/order-flow` reads
|
|
25
|
+
`Platform / Order flow`. Measured against the pinned likec4: the renderer reads that as a folder
|
|
26
|
+
path, so the view list mirrors the directory tree a human already navigates — one folder per
|
|
27
|
+
ancestor, the leaf's own label as the title — and two subsystems whose labels are equal are told
|
|
28
|
+
apart by their parents instead of appearing twice under one name. The box a view draws keeps the
|
|
29
|
+
subsystem's own label, unprefixed.
|
|
30
|
+
|
|
31
|
+
Because loam is now the one composing with `/`, it owns what a `/` inside an authored label would
|
|
32
|
+
do: measured, LikeC4 would show only the last segment and add a folder level the tree does not have,
|
|
33
|
+
so a slash in a marker's `title` becomes `∕` (U+2215) and the whole authored label stays visible as
|
|
34
|
+
one folder name. The alternative — a fixed `loam subsystems /` prefix — bought the same folder
|
|
35
|
+
without the churn and put loam's own name in a reader's UI; the tree's own words won.
|
|
36
|
+
|
|
37
|
+
Two costs, both real: the generated bytes move again, so a repo upgrading from `0.2.0-alpha.3`
|
|
38
|
+
reports one `subsystem.views-stale` until one `loam subsystem sync`; and renaming a PARENT's title
|
|
39
|
+
now rewrites the bytes of every view beneath it.
|
|
40
|
+
|
|
41
|
+
## [0.2.0-alpha.3] - 2026-09-03
|
|
42
|
+
|
|
43
|
+
The generated subsystem views, made readable — and the defect that hunt found underneath them.
|
|
44
|
+
Still a prerelease on the `alpha` line: `npm i @ybotok/loam@alpha` installs it. Nothing below is
|
|
45
|
+
breaking, and one entry is a change every repo NOTICES: the generated file's bytes move, so the
|
|
46
|
+
first `loam validate --all` after upgrading asks for one `loam subsystem sync`.
|
|
47
|
+
|
|
48
|
+
### Fixed — a `subsystem.views-stale` no command could clear
|
|
49
|
+
|
|
50
|
+
`loam validate --all` grades `architecture/subsystems.likec4` against the whole `architecture/`
|
|
51
|
+
project — the landscape merged with every `architecture/usecases/*.likec4` — while `loam subsystem
|
|
52
|
+
sync` and the write verbs rendered it from `landscape.likec4` alone. One use-case document declaring
|
|
53
|
+
an element bound to a filed service was therefore enough to make `sync` answer `current` while
|
|
54
|
+
`validate --all` exited 1 with `subsystem.views-stale`, whose message names `sync` as the repair.
|
|
55
|
+
Reproduced on `examples/docs`; the two now read the same documents. No code, flag or payload key
|
|
56
|
+
changes.
|
|
57
|
+
|
|
58
|
+
### Changed — the generated subsystem views carry a label and a boundary
|
|
59
|
+
|
|
60
|
+
`architecture/subsystems.likec4` exists only to be rendered, and in the renderer its views were the
|
|
61
|
+
least readable objects in a docs repo: no title, so LikeC4 showed the hex-escaped id
|
|
62
|
+
(`subsystem_platform__order_2dflow`), and leaf includes only, so a fleet drawn as ordinary
|
|
63
|
+
grouped C4 rendered flat. Every view now carries `title` (the marker's, or the directory name) and
|
|
64
|
+
`description` when the marker authors one, and draws a boundary: the model's own containing element
|
|
65
|
+
when one holds exactly the subsystem's members, a `group '<title>'` mirroring the directory subtree
|
|
66
|
+
when the landscape nests nothing, and — when the members are nested but no element owns exactly them
|
|
67
|
+
— no box plus one comment line, `// model: no boundary — …`, which is loam's whole answer to a tree
|
|
68
|
+
and a map that disagree. Nothing grades that comment: placement still carries no policy.
|
|
69
|
+
|
|
70
|
+
Marker text is normalized to one line and escaped before it is written (measured: a raw `'` in a
|
|
71
|
+
title is two parse errors and blanks the fleet's whole map in the renderer; `Ops' } view evil {` is
|
|
72
|
+
zero errors and injects a view). [SCHEMA.md](SCHEMA.md) states the rules.
|
|
73
|
+
|
|
74
|
+
What a repo notices, and what it costs:
|
|
75
|
+
|
|
76
|
+
- the first `loam validate --all` after upgrading reports exactly one `subsystem.views-stale` error
|
|
77
|
+
(exit 1) until one `loam subsystem sync` runs. `sync` does not `git add`, so the repair leaves one
|
|
78
|
+
unstaged one-file diff to commit;
|
|
79
|
+
- the boundary depends on which OTHER services sit under an element, so an unrelated adoption or
|
|
80
|
+
archive can flip a view from boxed to flat and restale the file. `loam archive` now withholds its
|
|
81
|
+
"living spec + landscape are now complete + current" line, prints one warning naming
|
|
82
|
+
`loam subsystem sync`, and adds `subsystemViewsStale: true` to its `--json` payload when it left
|
|
83
|
+
the file behind;
|
|
84
|
+
- `subsystem.views-stale` and `subsystem.view-id-collision` now carry `locations`: the generated
|
|
85
|
+
file (or `architecture/` when it does not exist yet) and the claimant document respectively. Both
|
|
86
|
+
used to fall back to the landscape, sending an editor to a file with nothing to fix in it.
|
|
87
|
+
|
|
88
|
+
No command, flag, exit code, envelope key or stable code string changes; `subsystem.views-stale` is
|
|
89
|
+
not in the `IssueCode` union and carries no `gates` key, so `loam archive` remains unaffected by it.
|
|
90
|
+
|
|
91
|
+
## [0.2.0-alpha.2] - 2026-09-02
|
|
92
|
+
|
|
93
|
+
The deployment axis, and the two defects the axis surfaced on its way in. Still a prerelease on the
|
|
94
|
+
`alpha` line: `npm i @ybotok/loam@alpha` installs it. Nothing below is breaking, but two entries are
|
|
95
|
+
changes a repo can NOTICE — a fleet that had already placed a `#obl-` tag on a `deploymentNode`
|
|
96
|
+
starts seeing findings it was never shown, and `loam status` stops asking an architecture-only
|
|
97
|
+
feature for a business spec.
|
|
98
|
+
|
|
99
|
+
### Added — the deployment axis: topology joined to requirements
|
|
100
|
+
|
|
101
|
+
A LikeC4 `deployment { }` block has always been legal in a docs repo and was completely unread. The
|
|
102
|
+
parser resolved every `instanceOf`, and after that no requirement could name a node, no `#obl-` tag
|
|
103
|
+
on a datacenter was graded, and the context pack an agent implements from did not mention topology
|
|
104
|
+
at all. Five things change, and a fleet that draws no topology is unaffected by every one of them —
|
|
105
|
+
the axis opts in on the block's existence, the way the obligation vocabulary opts in on its file.
|
|
106
|
+
|
|
107
|
+
- **`Covers:` gains a fourth entry form.** `node:<id>` names a deployment node or an instance, and
|
|
108
|
+
`node:a -> node:b` names an edge between them; both match literally. This is the form that lets an
|
|
109
|
+
architecture requirement about replication name the two clusters it is written about, and a
|
|
110
|
+
topology change that renames one now reports `covers.unknown` with the real ids offered — spelled
|
|
111
|
+
with the prefix, because that is the line the author has to type. Both sides of an edge carry the
|
|
112
|
+
prefix or neither; a bare id stays an element entry.
|
|
113
|
+
- **An obligation on a deployment object is graded.** `obligation.unknown`, `obligation.unapplied`
|
|
114
|
+
and `obligation.uncovered` now walk the topology as well as the logical model. This is a
|
|
115
|
+
behaviour change a repo can notice: a `#obl-` tag on a `deploymentNode` used to be invisible to
|
|
116
|
+
every check, so a fleet that had placed one starts seeing findings it was never shown. A
|
|
117
|
+
datacenter is owned by no service, so `obligation.uncovered` drops `subject` there and names the
|
|
118
|
+
requirement's home in general terms rather than picking a team.
|
|
119
|
+
- **A feature can bring topology.** `features/<FEAT>/deployment/<name>.likec4` is a create-only
|
|
120
|
+
document `loam archive` copies into `architecture/` and `loam unarchive` takes back — the third
|
|
121
|
+
whole-file slot, beside `usecases/` and `glossary/`. Say `extend` in it to add to a region the
|
|
122
|
+
living map already declares; two features may extend one region from documents of their own and
|
|
123
|
+
both archive. Two new codes, both errors and neither overridable: **`deployment.doc-exists`** when
|
|
124
|
+
the feature's document names a file the living tree already holds, and **`deployment.doc-invalid`**
|
|
125
|
+
when it cannot be read against the map the feature's own merge would leave behind. The second is
|
|
126
|
+
what makes the ordinary case legal — a document instancing a service only this feature's
|
|
127
|
+
`delta.likec4` adds resolves, because the corpus inside a feature window is feature ∪ living — and
|
|
128
|
+
refuses the case where the merge does not land what the document names. `delta.likec4` still
|
|
129
|
+
refuses a `deployment { }` block and now names this slot instead of the living landscape.
|
|
130
|
+
A third, **`deployment.doc-reserved`**, refuses a name loam owns inside `architecture/` —
|
|
131
|
+
`subsystems.likec4`, `landscape.likec4`, or a path under `usecases/` — whether or not the target
|
|
132
|
+
exists, because two of the three fail silently when it does not: nothing parses the generated
|
|
133
|
+
subsystem views and `loam subsystem sync` overwrites them wholesale.
|
|
134
|
+
- **`loam context` carries the topology.** `--json` gains `living.deployment` — the service's
|
|
135
|
+
instances, and the deployment edges touching them from either end.
|
|
136
|
+
- **One more statement in the adoption brief's unchecked list**, now sixteen: nothing here knows
|
|
137
|
+
whether the second cluster exists, is reachable, or holds the data a requirement claims, and no
|
|
138
|
+
check counts datacenters — how many a service needs is a decision the fleet writes down as a
|
|
139
|
+
requirement, never one loam evaluates.
|
|
140
|
+
|
|
141
|
+
### Fixed — the never-overridable archive refusal names what actually refused
|
|
142
|
+
|
|
143
|
+
It said `N element binding(s) name an illegal service id` for every code in that register, and had
|
|
144
|
+
done since the use-case slot put a second one there — about features that bind nothing. The headline
|
|
145
|
+
now counts the refusals and the closing line names their codes; each issue keeps its own sentence.
|
|
146
|
+
The `--json` envelope is unchanged: consumers branch on `issues[].code` and `overridable`, which is
|
|
147
|
+
what that split exists for.
|
|
148
|
+
|
|
149
|
+
### Fixed — `loam status` no longer invents work for an architecture-only feature
|
|
150
|
+
|
|
151
|
+
A feature whose only requirement delta is `specs/<svc>/arch.spec.md` reported two things that were
|
|
152
|
+
not true, and both are corrected. The `spec` row read `missing` and `next.author-spec` told the
|
|
153
|
+
author to write business requirements the change does not have — while `loam validate --feature`
|
|
154
|
+
accepted the same tree at exit 0 and `coherence`'s `service.no-requirement-delta` stayed silent,
|
|
155
|
+
because every other reader counts the service DIRECTORY and not which corpus is in it. And the
|
|
156
|
+
`verification` row read `blocked` with `blockedBy: ["delta", "spec", "openapi", "asyncapi"]`,
|
|
157
|
+
telling the reader there was nothing to derive a checklist from — while `featureChecklist` reads
|
|
158
|
+
`arch.spec.md` and `loam verify` on that feature returns a real checklist.
|
|
159
|
+
|
|
160
|
+
The fleet form carried the same defect independently, and it broke the rule stated one line below
|
|
161
|
+
it in its own source: `loam status` with no feature reported `missing <svc>/spec` and staged the
|
|
162
|
+
feature `missing`, over a feature `loam status <FEAT>` called `ready`. Both forms now accept either
|
|
163
|
+
corpus as the service's requirement delta, and `<svc>/spec` is named only when neither exists.
|
|
164
|
+
|
|
165
|
+
Three payload-visible consequences, all in `loam status --json`: `artifacts[]`'s `spec` row is
|
|
166
|
+
`required: false` when the service's `arch.spec.md` delta exists (it was unconditionally `true`),
|
|
167
|
+
`arch-spec` now counts as a verification feeder, so `blockedBy` gained `"arch-spec"` in the
|
|
168
|
+
one case that is still genuinely blocked — a feature with no requirement delta of either kind —
|
|
169
|
+
and `features[]`'s `missing` and `stage` no longer report an architecture-only feature as
|
|
170
|
+
incomplete. No finding code, flag or exit code changes. This is the change to read if a script
|
|
171
|
+
branched on `artifacts[].required` or on `features[].missing`.
|
|
172
|
+
|
|
173
|
+
The case is the one `arch.spec.md` exists for: an outbox, a retry policy, an alert — obligations no
|
|
174
|
+
business scenario was ever going to carry, so the feature that adds one has no business requirement
|
|
175
|
+
to write. Found by authoring loam's own architecture change through loam's own lifecycle, which is
|
|
176
|
+
the trigger ROADMAP's self-hosting section records for that axis.
|
|
177
|
+
|
|
13
178
|
## [0.2.0-alpha.1] - 2026-09-01
|
|
14
179
|
|
|
15
180
|
The first release of the 0.2 line, and the whole accumulated body since `0.1.0-beta.3` — 116
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ybotok/loam/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/@ybotok/loam)
|
|
9
9
|
|
|
10
|
-
> **Pre-release: `0.2.0-alpha.
|
|
10
|
+
> **Pre-release: `0.2.0-alpha.4`**, the third published release of the 0.2 line and the newest published
|
|
11
11
|
> version of [`@ybotok/loam`](https://www.npmjs.com/package/@ybotok/loam). It publishes under the
|
|
12
12
|
> `alpha` npm dist-tag: `npm i @ybotok/loam@alpha`, or the exact version, always installs this
|
|
13
13
|
> line. `latest` is moved onto a release by hand once it has published, so a plain
|
|
@@ -108,7 +108,7 @@ both halves are written down: the question in the brief or the checklist, the an
|
|
|
108
108
|
|
|
109
109
|
Most of this category will tell you what it checks. loam ships the other list too, in the binary:
|
|
110
110
|
**[`src/core/brief/unchecked.ts`](https://github.com/ybotok/loam/blob/main/src/core/brief/unchecked.ts)
|
|
111
|
-
is
|
|
111
|
+
is sixteen statements of what no check will ever tell you**, printed into the adoption brief beside
|
|
112
112
|
the checks that do run. It says, in loam's own words, that nothing here knows whether the model is
|
|
113
113
|
the architecture the code actually has, whether a requirement is *true*, whether an `operationId` is
|
|
114
114
|
the one the code serves, or whether `sources` names the files anybody read. And it says the one that
|
|
@@ -358,7 +358,7 @@ cannot hold.
|
|
|
358
358
|
| `loam new <FEAT>` | `--title <text>` `--touches <id>` `--new-service <id>` `--capability <id>` | Scaffold a feature: intent, C4 delta, a requirement delta per service named by `--touches`/`--new-service`, and a capability delta per promise named by `--capability` (all repeatable) |
|
|
359
359
|
| `loam show <service\|FEAT>` | `--type <kind>` | Everything loam knows about one service or feature. A feature's JSON includes a semantic `review` pack: intent, exact architecture objects, API/event slices, dependencies, artifacts, verification, blockers and next actions. `--type` forces an ambiguous reading |
|
|
360
360
|
| `loam subsystem <verb> [names...]` | `--into <name>` `--under <name>` `--title <text>` `--description <text>` `--owner <name>` | Manage the grouping tree under `services/` — a navigable tree no identity depends on. `new`/`rm`, `move … --into`, `rename`, `list`, `history`, `sync` |
|
|
361
|
-
| `loam adopt` | `--service <id>` `--subsystem <name>` `--targets` | Brief an agent to write this service's baseline into the docs repo as `draft`: the target paths, the grammar of each, what the landscape already says, and the checks that follow. Writes nothing. `--targets` narrows it to what varies by service — the walk, the 37 checks and the
|
|
361
|
+
| `loam adopt` | `--service <id>` `--subsystem <name>` `--targets` | Brief an agent to write this service's baseline into the docs repo as `draft`: the target paths, the grammar of each, what the landscape already says, and the checks that follow. Writes nothing. `--targets` narrows it to what varies by service — the walk, the 37 checks and the sixteen statements of what nothing checks are the same bytes everywhere, so a `full` field names the one run that carries them: 42,873 bytes to 17,805 |
|
|
362
362
|
| `loam delta <FEAT>` | `--service <id>` | Project a feature onto one service: the intent, its requirement deltas with every body and Given/When/Then line verbatim, the endpoints it adds or retires, and the edges around it. Doubles as a coding-agent task |
|
|
363
363
|
| `loam gherkin [<FEAT>]` | `--service <id>` `--dry-run` | Emit spec scenarios as digest-stamped Gherkin `.feature` files into the service repo's `<gherkinDir>/loam/` |
|
|
364
364
|
| `loam rebase [<FEAT>]` | `--service <id>` `--dry-run` `--living` | Pin the feature to the living versions it was written against, on the requirement axis and the contract axis. `--living` takes no feature: it pins the living corpus's `Realizes:` entries to the capability requirements they name, which is what makes `capability.realizes-stale` able to fire later |
|
|
@@ -486,7 +486,7 @@ always preserved byte for byte, with the entry printed for manual merge.
|
|
|
486
486
|
maps, what is lost, what must be added.
|
|
487
487
|
- [ROADMAP.md](ROADMAP.md) — the evidence-backed improvement plan, priorities, non-goals and exit
|
|
488
488
|
criteria.
|
|
489
|
-
- [CHANGELOG.md](CHANGELOG.md) — released beta.1–alpha.
|
|
489
|
+
- [CHANGELOG.md](CHANGELOG.md) — released beta.1–alpha.4 plus the changes on `main` under
|
|
490
490
|
`[Unreleased]`.
|
|
491
491
|
- [docs/BENCHMARKS.md](https://github.com/ybotok/loam/blob/main/docs/BENCHMARKS.md) — what a
|
|
492
492
|
fleet-sized run costs, with the method, the machine and the committed runs behind every number.
|
|
@@ -522,12 +522,12 @@ authored-capability axes with the `Realizes:` join, use cases graded as `dynamic
|
|
|
522
522
|
`architecture/obligations.yaml` architectural obligations checked through `#obl-` tags and
|
|
523
523
|
`Covers:`. Known limits, each with its owner in [ROADMAP.md](ROADMAP.md): the complete gate still
|
|
524
524
|
needs repeatable CI and installed-package evidence observed from an actual push. Speculative
|
|
525
|
-
`render`, health composition and UI generation come later. Behind that status stand **
|
|
525
|
+
`render`, health composition and UI generation come later. Behind that status stand **157 test files** (counted 2026-09-02): the count is graded
|
|
526
526
|
against a live readdir by `test/docs-facts.test.ts`, so this sentence fails the suite the moment it
|
|
527
527
|
trails the tree.
|
|
528
528
|
|
|
529
529
|
The package is published as [`@ybotok/loam`](https://www.npmjs.com/package/@ybotok/loam), currently
|
|
530
|
-
`0.2.0-alpha.
|
|
530
|
+
`0.2.0-alpha.4`: the unscoped `loam` name is taken by an unrelated GDAL wrapper, so the package ships
|
|
531
531
|
under the maintainer's own npm user scope. Releases are tag-driven and maintainer-only
|
|
532
532
|
([CONTRIBUTING.md](CONTRIBUTING.md)); the version this line names is pinned to `package.json` by a
|
|
533
533
|
drift test, so it cannot silently trail a release.
|
package/ROADMAP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
_Assessed 2026-
|
|
3
|
+
_Assessed 2026-09-03._
|
|
4
4
|
|
|
5
5
|
This is loam's canonical improvement sequence. It records priorities and exit criteria, not delivery
|
|
6
6
|
dates. An item is complete only when its exit criteria are supported by repeatable evidence; moving
|
|
@@ -18,20 +18,21 @@ acceptance tests, and implementation evidence remain ordinary files in repositor
|
|
|
18
18
|
state inside a service. The CLI is small enough to audit, and every writer — not only archive — now
|
|
19
19
|
commits through a locked, journaled transaction that a crash cannot leave half-applied.
|
|
20
20
|
|
|
21
|
-
The tree contains **
|
|
21
|
+
The tree contains **427 TypeScript modules in 130 source packages**, with an acyclic package graph
|
|
22
22
|
checked by
|
|
23
23
|
[scripts/package-graph.mjs](https://github.com/ybotok/loam/blob/main/scripts/package-graph.mjs). The
|
|
24
24
|
CLI exposes **29 commands** from [src/cli.ts](https://github.com/ybotok/loam/blob/main/src/cli.ts),
|
|
25
|
-
and the suite stands at **
|
|
25
|
+
and the suite stands at **157 test files**. Those four counts are deliberately stated OUTSIDE the
|
|
26
26
|
dated snapshot below: each derives from the tree in one readdir, so
|
|
27
27
|
[test/docs-facts.test.ts](https://github.com/ybotok/loam/blob/main/test/docs-facts.test.ts) grades
|
|
28
28
|
them live and this paragraph cannot quietly trail the code the way its predecessor did.
|
|
29
29
|
|
|
30
|
-
_Measured 2026-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
_Measured 2026-09-03 at `0.2.0-alpha.4`, and dated because neither number has a cheap derivation:_
|
|
31
|
+
lint, typecheck, `npm run arch:check` and `npm run meta:check` green; `npm test` passing
|
|
32
|
+
**3,410/3,410 tests**, with two `skipIf(asRoot)` cases the root gate container cannot run; the
|
|
33
|
+
coverage gate passing with **92.26% statements, 84.06% branches, 96.62% functions, and 94.20%
|
|
34
|
+
lines** against its thresholds of 91 / 82 / 95 / 93; and `npm run release:check` plus
|
|
35
|
+
`npm run test:package` green against the real tarball.
|
|
35
36
|
|
|
36
37
|
One qualification remains, and it is observation rather than code: nothing has been pushed since the
|
|
37
38
|
CI `stability` job and the tarball-reading package smoke landed, so repeatable CI executions — the
|
|
@@ -98,14 +99,16 @@ The strongest foundations to preserve are:
|
|
|
98
99
|
## Now
|
|
99
100
|
|
|
100
101
|
Nothing is queued behind code. Every integrity, enforcement and lifecycle item that preceded this
|
|
101
|
-
has landed (see [Recently landed](#recently-landed)) — the authored business axis
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
has landed (see [Recently landed](#recently-landed)) — the authored business axis, the use-case
|
|
103
|
+
axis, and as of 2026-09-02 the deployment axis, which is the first item this file ever wrote
|
|
104
|
+
directly into this section rather than promoting from [Later](#later--promote-only-from-evidence).
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
### Release evidence, unchanged
|
|
107
|
+
|
|
108
|
+
What is left is the one thing this file cannot close by writing code, and it stays
|
|
109
|
+
where it is rather than being promoted for the shape of it: the CI `stability` job and the
|
|
110
|
+
installed-package smoke have still to be OBSERVED green from a pushed commit. That is release
|
|
111
|
+
evidence to collect, not an improvement to make.
|
|
109
112
|
|
|
110
113
|
An item returns to this section from [Later](#later--promote-only-from-evidence) when fleet evidence
|
|
111
114
|
names its operator, repeated task, failure mode, frequency, current workaround and measurable
|
|
@@ -122,6 +125,125 @@ changes, exit criteria, and what each review surfaced — are in this file's his
|
|
|
122
125
|
working tree and not committed, and the range goes in with the commit rather than being guessed at
|
|
123
126
|
here.
|
|
124
127
|
|
|
128
|
+
#### The generated subsystem views — a label, a boundary, and one reader of the map
|
|
129
|
+
|
|
130
|
+
**Landed 2026-09-03.** Three problem reports from a fleet running loam, one axis. The generated
|
|
131
|
+
`architecture/subsystems.likec4` exists only to be rendered and was the least readable object in a
|
|
132
|
+
docs repo: no `title`, so the renderer showed the hex-escaped view id, and leaf includes only, so a
|
|
133
|
+
fleet drawn as grouped C4 rendered flat. Each view now carries the marker's title (the directory
|
|
134
|
+
name when it has none) and description, and draws a boundary the LANDSCAPE decides: the model's own
|
|
135
|
+
containing element when one holds exactly the members, a `group` mirroring the directory subtree
|
|
136
|
+
when the map nests nothing, and one `// model: no boundary — …` comment when the two disagree.
|
|
137
|
+
That comment is the whole answer to the drift the third report asked about — disclosure in a
|
|
138
|
+
generated artifact, never a finding, because grading placement against the map is the policy the
|
|
139
|
+
tree is defined not to carry. The graded alternative is written down under
|
|
140
|
+
[Later](#later--promote-only-from-evidence) with the evidence bar it has to clear.
|
|
141
|
+
|
|
142
|
+
The hunt found what neither report did: `validate --all` graded that file against the whole
|
|
143
|
+
`architecture/` project while every writer rendered it from `landscape.likec4` alone, so a
|
|
144
|
+
use-case document binding one element could produce a `subsystem.views-stale` that
|
|
145
|
+
`loam subsystem sync` reported as `current` — reproduced on `examples/docs`, and fixed by pointing
|
|
146
|
+
both sides at the same read.
|
|
147
|
+
|
|
148
|
+
#### The deployment axis — topology joined to requirements
|
|
149
|
+
|
|
150
|
+
**Promoted 2026-09-02, and authored as a feature in loam's own docs repo** rather than as an item
|
|
151
|
+
text here: `meta/docs/features/archive/FEAT-1-the-deployment-axis/`, whose `intent.md` carries the
|
|
152
|
+
four measurements and whose `specs/loam/arch.spec.md` carries the five rules the change establishes,
|
|
153
|
+
each with scenarios. This entry is the decision and the gate; that feature is the specification, and
|
|
154
|
+
the two must not be allowed to become two accounts of the same change.
|
|
155
|
+
|
|
156
|
+
**Landed 2026-09-02, complete.** Every step is in, and step 4 was corrected rather than built
|
|
157
|
+
(below) — the item specified two finding codes that the implementation showed it should not mint.
|
|
158
|
+
FEAT-1 archived once every edge it claimed existed —
|
|
159
|
+
and it archived having been CORRECTED against the tree twice, which is the argument for the route
|
|
160
|
+
rather than a footnote to it: the delta predicted a `core/kernel/` dependency that turned out to be
|
|
161
|
+
type-only (a brand costs an annotation, not an import) and missed `core/coherence/` entirely.
|
|
162
|
+
`npm run meta:check` was red for the whole of the build and went green with the archive, which is
|
|
163
|
+
the axis working rather than a gap in it.
|
|
164
|
+
|
|
165
|
+
The whole change is reviewable as five commits on `feat/deployment-axis`, and the one thing to read
|
|
166
|
+
first is what the implementation taught the plan: two of the codes this item specified were not
|
|
167
|
+
minted, and the reasons are in step 4.
|
|
168
|
+
|
|
169
|
+
**What is wrong today.** A LikeC4 `deployment { }` block is legal in a docs repo and completely
|
|
170
|
+
unread. The parser resolves every `instanceOf` in it — a container renamed out from under a
|
|
171
|
+
deployment node fails the gate as `landscape.invalid` — and after that nothing: no requirement can
|
|
172
|
+
name a node, no report counts one, and the context pack an agent implements from does not mention
|
|
173
|
+
topology at all. Two of the four measurements taken on a copy of `examples/docs` are the reason this
|
|
174
|
+
is an item rather than a wish. A `#obl-` tag on a `deploymentNode` is invisible to
|
|
175
|
+
`validate/fleet/obligations.ts`, which walks the logical model only — the same undeclared tag raises
|
|
176
|
+
`obligation.unknown` on a container and nothing on a datacenter, which is fail-open and a defect on
|
|
177
|
+
its own terms. And `parsedModel().deployment` already returns nodes with their tags, instances
|
|
178
|
+
carrying the id of the logical element each one instances, and relationships carrying metadata, so
|
|
179
|
+
the read costs no second parse, no new file format and no change to the frozen CLI surface.
|
|
180
|
+
|
|
181
|
+
**Required changes**, in the order the dependency graph forces:
|
|
182
|
+
|
|
183
|
+
1. Walk the deployment model in every `#obl-` grade. Fail-open becomes fail-closed; no new code.
|
|
184
|
+
2. A fourth `Covers:` entry form, `node:<id>`, with the same prefix accepted on either side of the
|
|
185
|
+
edge form. No new code.
|
|
186
|
+
3. The parse adapter in `src/core/c4/parsed/`, and the deployment records added to
|
|
187
|
+
`test/likec4-model-parity.test.ts` — a two-stage substitution nothing measures is an assumption.
|
|
188
|
+
`src/core/c4/` holds exactly five files, so this is a sub-package and not a sixth module.
|
|
189
|
+
4. **No new finding code, and this is a correction the implementation earned.** The item planned
|
|
190
|
+
two. The first, for a tagged node or edge no living arch requirement covers, turned out to be
|
|
191
|
+
`obligation.uncovered` asked of a second model: once step 1 walks the topology, the existing code
|
|
192
|
+
answers it, and a caller acts identically either way — write a `Covers:` line. A second code for
|
|
193
|
+
one question is exactly what the add-a-code checklist refuses. The second, for a container no
|
|
194
|
+
deployment instances, is declined outright: it asserts that everything modelled ought to be
|
|
195
|
+
deployed somewhere, which is a COMPLETENESS claim, and completeness is the one thing
|
|
196
|
+
`src/core/brief/unchecked.ts` says loam never checks. It is the same category error as the
|
|
197
|
+
at-least-two-datacenters rule below, one axis over.
|
|
198
|
+
5. `features/<FEAT>/deployment/<name>.likec4`, create-only and copied whole — `core/usecases/delta/`
|
|
199
|
+
one axis over. It works because `extend` was measured to resolve across documents of one project,
|
|
200
|
+
so no text splice is needed; `delta-blocks.ts` goes on refusing a `deployment { }` block inside
|
|
201
|
+
`delta.likec4` and its message names the new slot. The collision is on the FILE, never on what is
|
|
202
|
+
inside it: two features may both extend one living region from documents of their own and both
|
|
203
|
+
archive, which is the shape the fleet-wide change actually takes.
|
|
204
|
+
|
|
205
|
+
**Graded against the merge preview**, which shipped one commit behind the slot and is the
|
|
206
|
+
second code, `deployment.doc-invalid` (error, never overridable). This axis needs the post-merge
|
|
207
|
+
corpus more literally than the use-case one: a feature that stands a NEW service up in a cluster
|
|
208
|
+
declares it in `delta.likec4` and instances it in the topology, so the element the document names
|
|
209
|
+
exists only after the merge. The staging that makes that readable moved OUT of
|
|
210
|
+
`core/usecases/delta/overlay.ts` into `core/c4/project/staged.ts` rather than being copied — the
|
|
211
|
+
two axes would have disagreed first about the merge preview, which is the one thing both
|
|
212
|
+
refusals rest on. The extraction dropped `core/usecases/` → `core/kernel/`, which is a one-line
|
|
213
|
+
edge and was edited into the model directly.
|
|
214
|
+
6. The surfaces: the topology in the context pack (`context --json`'s `living.deployment`), the code
|
|
215
|
+
table `test/codes-drift.test.ts` requires, and one more statement in
|
|
216
|
+
`src/core/brief/unchecked.ts`. `loam explain` needed nothing — it parses the /loam-check fix
|
|
217
|
+
tables at runtime, so the rows added there ARE the entries. The scorecard row the item listed was
|
|
218
|
+
dropped: the fleet scorecard aggregates at the bound `service`, and a topology row there would
|
|
219
|
+
count deployment nodes against a denominator no service owns.
|
|
220
|
+
|
|
221
|
+
**Exit criteria.** Every step above green under the full gate; `covers.unknown` demonstrated firing
|
|
222
|
+
on a covered node that a topology change renamed, which is the acceptance criterion the trigger
|
|
223
|
+
names; the parity suite extended and shown to fail against an un-extended adapter; and
|
|
224
|
+
`test/self-model.test.ts` re-pinned once FEAT-1 archives.
|
|
225
|
+
|
|
226
|
+
**Out of scope, and each refusal is load-bearing.** No reading of Terraform, Helm or cluster state —
|
|
227
|
+
that is the extractor the non-goals forbid. No check that a service is deployed in at least two
|
|
228
|
+
datacenters: it would need `criticality` promoted from prose to a read field, and it would turn
|
|
229
|
+
`architecture/obligations.yaml` into the policy engine that file says in its own words it is not. A
|
|
230
|
+
multi-datacenter rule is an authored requirement whose `Covers:` line names the nodes; loam grades
|
|
231
|
+
the join and never the architecture. And no `environment` concept above LikeC4, because
|
|
232
|
+
`deploymentNode` is that mechanism and the fleet names its own kinds.
|
|
233
|
+
|
|
234
|
+
**The evidence, and its one gap stated plainly.** Operator: a team standing up a standby cluster in
|
|
235
|
+
a second datacenter. Repeated task: keeping RTO/RPO requirements attached to a topology that keeps
|
|
236
|
+
changing. Failure mode: a requirement about replication staying green after the node it described
|
|
237
|
+
was renamed or removed. Frequency: every topology change, which is the cadence a migration runs at.
|
|
238
|
+
Current workaround: a `Covers:` line that resolves to nothing, or no written requirement at all.
|
|
239
|
+
Acceptance criterion: a topology change that orphans a covered requirement is convicted without a
|
|
240
|
+
human noticing it first. **That is one operator, not two.** The maintainer judged the trigger
|
|
241
|
+
sufficient on the same basis recorded for landscape scaling and the authored business axis, and the
|
|
242
|
+
[Later](#later--promote-only-from-evidence) exit criteria are amended below to say so rather than
|
|
243
|
+
leaving the two paragraphs to contradict each other. Steps 1 and 2 do not rest on that judgement at
|
|
244
|
+
all: the first is a fail-open defect and the second is a grammar entry, and neither is a new axis.
|
|
245
|
+
|
|
246
|
+
|
|
125
247
|
- **A feature can bring a use case.** `features/<FEAT>/usecases/<name>.likec4` — a views-only
|
|
126
248
|
document `loam archive` copies into `architecture/usecases/` and `loam unarchive` takes back,
|
|
127
249
|
create-only, refused by `usecase.flow-exists` when the living tree already holds that file. All
|
|
@@ -247,6 +369,18 @@ item gets promoted — it went from here to `## Now` and from there to
|
|
|
247
369
|
- **UI generation:** begin with a disposable projection over the stable JSON contract only after CLI
|
|
248
370
|
consumers demonstrate a repeated navigation problem. It must not introduce a second mutable state,
|
|
249
371
|
hidden workflow state, or a required service.
|
|
372
|
+
- **A graded subsystem/model correspondence:** a fleet that draws its landscape as grouped C4 states
|
|
373
|
+
its grouping twice — once as directories under `services/`, once as the nesting of the elements —
|
|
374
|
+
and `loam subsystem move` changes the first while the second stays put. Since 2026-09-02 the
|
|
375
|
+
generated views file DISCLOSES that (the `// model: no boundary — …` line; see
|
|
376
|
+
[SCHEMA.md](SCHEMA.md)), and nothing grades it, because grading placement against the map is the
|
|
377
|
+
policy the tree is defined not to carry. The shape a graded version would take is known and
|
|
378
|
+
measured: an author's opt-in `metadata { subsystem '<name>' }` on the grouping element, read
|
|
379
|
+
through the existing metadata reader, the NAME rather than the path (names are unique in one flat
|
|
380
|
+
namespace and survive a move, which a path binding would not), with the check structurally silent
|
|
381
|
+
for every fleet that never writes one. Promote only on evidence from a second fleet, after living
|
|
382
|
+
with the disclosure — the first report classified itself `inconclusive`, and one fleet is not
|
|
383
|
+
enough to qualify an invariant that says placement is never part of any identity.
|
|
250
384
|
- **Landscape scaling:** retain one landscape while conflicts are exceptional. If same-service
|
|
251
385
|
conflicts become routine — the current trigger in [SCHEMA.md](SCHEMA.md) is weekly rather than
|
|
252
386
|
monthly — evaluate service-owned model files plus a thin global cross-service map. Migration must
|
|
@@ -264,8 +398,11 @@ item gets promoted — it went from here to `## Now` and from there to
|
|
|
264
398
|
|
|
265
399
|
Exit criteria for promoting a Later item:
|
|
266
400
|
|
|
267
|
-
- Evidence comes from at least two independent operators or fleets, except landscape scaling
|
|
268
|
-
authored business axis, whose recorded
|
|
401
|
+
- Evidence comes from at least two independent operators or fleets, except landscape scaling, the
|
|
402
|
+
authored business axis and — added 2026-09-02 — the deployment axis in [Now](#now), whose recorded
|
|
403
|
+
triggers are sufficient. Three exceptions is where this criterion starts describing the rule
|
|
404
|
+
rather than the exception: the next single-operator promotion has to say why the criterion should
|
|
405
|
+
survive, or retire it.
|
|
269
406
|
- An ADR compares the existing workflow, external tooling, and the smallest loam-owned change.
|
|
270
407
|
- A prototype is measured against a predeclared acceptance criterion and preserves every non-goal
|
|
271
408
|
below.
|
|
@@ -276,7 +413,7 @@ Exit criteria for promoting a Later item:
|
|
|
276
413
|
|
|
277
414
|
Placed in a section of its own, and the placement is the argument. This is not a `## Now` item, and
|
|
278
415
|
nothing here may be mistaken for one. It is not a "Later" candidate either: promotion
|
|
279
|
-
from that section requires evidence from at least two independent operators or fleets (bar the
|
|
416
|
+
from that section requires evidence from at least two independent operators or fleets (bar the three
|
|
280
417
|
whose recorded triggers were judged sufficient when they were written), and loam's own repository is
|
|
281
418
|
one operator by construction, so a Later entry could only ever be a permanent exception to the rule
|
|
282
419
|
it sits under. Nor is its value speculative — the trigger below is a defect this repository has
|
package/SCHEMA.md
CHANGED
|
@@ -164,9 +164,41 @@ check uses; a member nothing models is omitted, which `landscape.service-unmodel
|
|
|
164
164
|
reports). The output is deterministic and line-oriented — subsystems sorted by path, members by id,
|
|
165
165
|
one `include` per line, no timestamps, no absolute paths — so it is byte-reproducible on any machine
|
|
166
166
|
and two concurrent moves into different groups merge in git without intervention.
|
|
167
|
+
|
|
168
|
+
Each view carries the **label a human reads**: `title`, and `description` when the marker authors
|
|
169
|
+
one. The title is the subsystem's **path** — every marked ancestor's label and its own, joined with
|
|
170
|
+
` / `, so `services/platform/order-flow` reads `Platform / Order flow` — and each label is the
|
|
171
|
+
marker's `title`, falling back to the directory name (an unlabelled view is shown by its id, which
|
|
172
|
+
is hex-escaped for injectivity and reads as `subsystem_platform__order_2dflow`). LikeC4
|
|
173
|
+
reads that path as folders: the view list mirrors the directory tree, showing one folder per
|
|
174
|
+
ancestor and the leaf's own label as the title. A `/` **inside** a label becomes `∕` (U+2215), so an
|
|
175
|
+
authored slash can neither hide half of what somebody wrote nor invent a folder level the tree does
|
|
176
|
+
not have. Both strings are somebody's prose written into syntax, so both are normalized to one line
|
|
177
|
+
and escaped. Titles are not unique — only ids are — and renaming a parent's title rewrites every
|
|
178
|
+
view beneath it.
|
|
179
|
+
|
|
180
|
+
The **boundary** each view draws is a fact about the landscape, never a setting:
|
|
181
|
+
|
|
182
|
+
1. an element that holds *exactly* the subsystem's members and nothing else is included beside the
|
|
183
|
+
leaves, so the renderer nests them in the box the author drew, under the author's title;
|
|
184
|
+
2. a landscape that nests nothing puts the services in a `group '<title>'` of loam's own, mirroring
|
|
185
|
+
the directory subtree, so a parent subsystem shows its children as boxes rather than one pile;
|
|
186
|
+
3. members that ARE nested with no element owning exactly them get no box and one comment line —
|
|
187
|
+
`// model: no boundary — <id> also holds services outside this subsystem.` — because a synthetic
|
|
188
|
+
box would look identical whether or not the tree and the map agree. That comment is the whole of
|
|
189
|
+
what loam says about the two disagreeing: nothing grades it, nothing fails on it, and it lands in
|
|
190
|
+
the diff of the commit that caused it. **The tree and any `group` nesting in the landscape stay
|
|
191
|
+
independent**; `loam subsystem move` never rewrites the map, and no finding compares them.
|
|
192
|
+
|
|
193
|
+
Because the boundary depends on which *other* services sit under an element, an unrelated adoption
|
|
194
|
+
or archive can flip a view from boxed to flat — a new reason to see `subsystem.views-stale`, and why
|
|
195
|
+
`loam archive` withholds its "complete + current" line and names `loam subsystem sync` when it left
|
|
196
|
+
the file behind.
|
|
197
|
+
|
|
167
198
|
`loam subsystem sync` is the one regenerator (it also *removes* the file when the tree has no
|
|
168
199
|
subsystems), every `loam subsystem` write verb regenerates it inside the same transaction, and
|
|
169
|
-
`validate --all` grades staleness by **
|
|
200
|
+
`validate --all` grades staleness by **content comparison** (line endings normalized; a CRLF
|
|
201
|
+
checkout changes no fact) — exactly one error,
|
|
170
202
|
`subsystem.views-stale`, on exactly that file; nothing in loam ever parses it: a views-only document
|
|
171
203
|
does not parse standalone (its `include` lines reference elements the landscape defines), and it
|
|
172
204
|
holds **static** views, whose contents loam never reads in any document. Only the LikeC4 renderer,
|
|
@@ -142,19 +142,23 @@ export async function gate(config, featureId, opts, say) {
|
|
|
142
142
|
// the rest. The register is issue.ts's `approveOverrides` — the same data
|
|
143
143
|
// the `overridable` key in the envelope is resolved from — never a code
|
|
144
144
|
// string spelled here, because two spellings of "what the flag moves" would
|
|
145
|
-
// drift.
|
|
146
|
-
// the id grammar refuses,
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// mechanical fact about the
|
|
151
|
-
// feature
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
145
|
+
// drift. What the register HOLDS has grown: it began as one code (a
|
|
146
|
+
// `metadata { service }` binding the id grammar refuses, where the merge
|
|
147
|
+
// would splice the name into the living map verbatim and probe
|
|
148
|
+
// `services/<binding>/` with it) and now holds the create-only collisions of
|
|
149
|
+
// every whole-file axis besides. They share the doctrine and nothing else —
|
|
150
|
+
// each is a mechanical fact about what the merge would write, never a
|
|
151
|
+
// judgment about the feature — which is why the message below counts them
|
|
152
|
+
// and lets each issue say its own sentence.
|
|
153
|
+
//
|
|
154
|
+
// IT USED TO NAME BINDINGS, and had been wrong for every other code in the
|
|
155
|
+
// register since the use-case slot landed: a feature whose flow collided
|
|
156
|
+
// with a living one was refused with "1 element binding(s) name an illegal
|
|
157
|
+
// service id", about a feature that binds nothing. A refusal headline that
|
|
158
|
+
// describes the wrong breach sends its reader to the wrong file first.
|
|
155
159
|
const nonOverridable = issues.filter((i) => !approveOverrides(i));
|
|
156
160
|
if (nonOverridable.length > 0) {
|
|
157
|
-
const msg = `archive ${id} — BLOCKED: ${nonOverridable.length}
|
|
161
|
+
const msg = `archive ${id} — BLOCKED: ${nonOverridable.length} issue(s) --approve does not override; each names what the merge would write`;
|
|
158
162
|
if (json) {
|
|
159
163
|
refuseJson("not-coherent", msg, issues, `features/${dirName}`);
|
|
160
164
|
return null;
|
|
@@ -166,7 +170,9 @@ export async function gate(config, featureId, opts, say) {
|
|
|
166
170
|
// line says which of the marks the flag cannot move.
|
|
167
171
|
for (const i of issues)
|
|
168
172
|
console.error(` ${SEVERITY_MARK[i.severity]} ${i.message}`);
|
|
169
|
-
console.error(`\nOf these, --approve does not override
|
|
173
|
+
console.error(`\nOf these, --approve does not override ${nonOverridable.length}: ${[...new Set(nonOverridable.map((i) => i.code))].sort().join(", ")}. ` +
|
|
174
|
+
"Each is a mechanical fact about what the merge would write — a name loam cannot resolve, or a file it would replace wholesale — not a judgement the flag can move. " +
|
|
175
|
+
"Fix those; the other issues gate as usual.");
|
|
170
176
|
sayExplain("not-coherent");
|
|
171
177
|
process.exitCode = 1;
|
|
172
178
|
return null;
|