@milehimikey/em 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +99 -22
- package/.claude/skills/event-modeling/reference/conform.md +230 -0
- package/.claude/skills/event-modeling/reference/em-dsl.md +93 -26
- package/.claude/skills/event-modeling/reference/extract.md +12 -5
- package/.claude/skills/event-modeling/reference/methodology.md +103 -21
- package/.claude/skills/event-modeling/templates/conformance-report.md +69 -0
- package/.claude/skills/event-modeling/templates/model-readme.md +9 -0
- package/.claude/skills/event-modeling/templates/slice.md +15 -1
- package/.claude/skills/event-modeling/templates/state.md +1 -0
- package/README.md +17 -4
- package/dist/cli/changelog-git.d.ts +53 -0
- package/dist/cli/changelog-git.js +112 -0
- package/dist/cli/changelog-git.js.map +1 -0
- package/dist/cli.js +103 -5
- package/dist/cli.js.map +1 -1
- package/dist/emit/changelog.d.ts +57 -0
- package/dist/emit/changelog.js +143 -0
- package/dist/emit/changelog.js.map +1 -0
- package/dist/emit/diffJson.d.ts +17 -0
- package/dist/emit/diffJson.js +74 -0
- package/dist/emit/diffJson.js.map +1 -0
- package/dist/emit/json.d.ts +1 -0
- package/dist/emit/json.js +1 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/model/edges.js +7 -16
- package/dist/model/edges.js.map +1 -1
- package/dist/model/validate.js +154 -0
- package/dist/model/validate.js.map +1 -1
- package/dist/render/drawEdges.d.ts +3 -1
- package/dist/render/drawEdges.js +119 -7
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/render.js +5 -3
- package/dist/render/render.js.map +1 -1
- package/dist/render/svgGeometry.d.ts +13 -0
- package/dist/render/svgGeometry.js +47 -0
- package/dist/render/svgGeometry.js.map +1 -1
- package/package.json +3 -2
|
@@ -5,11 +5,13 @@ description: >-
|
|
|
5
5
|
the 7 steps of Event Modeling and the 4 patterns (State Change, State View, Automation,
|
|
6
6
|
Translation), and produce implementation-ready slice design documents. Use when the user wants
|
|
7
7
|
to event-model a business process or system, do event modeling / event storming, design slices,
|
|
8
|
-
build or edit an `.em` model, run the em tool,
|
|
9
|
-
event model from an existing system or codebase (event-driven or legacy/procedural)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`
|
|
8
|
+
build or edit an `.em` model, run the em tool, extract / reverse-engineer a current-state
|
|
9
|
+
event model from an existing system or codebase (event-driven or legacy/procedural), or check
|
|
10
|
+
a ratified model for drift against the codebase that implements it. Works in resumable phases
|
|
11
|
+
via an argument: `discover` (steps 1-4, greenfield), `extract` (current-state model of an
|
|
12
|
+
existing system), `model` (steps 5-7), `slice` (deep slice specs), `conform` (drift check
|
|
13
|
+
against the codebase), plus `watch` and `validate`. With no argument it resumes from the saved
|
|
14
|
+
state file.
|
|
13
15
|
---
|
|
14
16
|
|
|
15
17
|
# Event Modeling with `em`
|
|
@@ -46,11 +48,31 @@ validation rules) before doing real work — they are the source of truth. Templ
|
|
|
46
48
|
triggers **no** command — the analog of `view → ui`). **Repeat read models** across slices so the
|
|
47
49
|
timeline flows left-to-right — put each repeat **right after the event that feeds it**, sourcing
|
|
48
50
|
only that one adjacent event, so every arrow is short (a read model far from its source events
|
|
49
|
-
draws long
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
draws a long arrow whose head lands columns away, making the *write* slice read as dangling —
|
|
52
|
+
keep a sub-flow that detours into another context together, not parked at the end of the model).
|
|
53
|
+
Declare every instance after the first with
|
|
54
|
+
**`view X again from "..."`** — `again` instances are exempt from the duplicate-name warning even
|
|
55
|
+
when referenced, and each reference resolves to the right instance. Slice order matters: a
|
|
56
|
+
reaction must be directly followed by its command slice, and a read slice must not be immediately
|
|
52
57
|
followed by a command slice (see `em-dsl.md`).
|
|
53
58
|
See `reference/methodology.md` (State View) and `reference/em-dsl.md`.
|
|
59
|
+
- **Never connect two instances of one read model.** The repeat is a timeline device, not a flow:
|
|
60
|
+
continuity is implied by the shared name, and the events arriving at each instance are what show
|
|
61
|
+
it changing. An arrow between instances says the read model feeds itself, and is an error.
|
|
62
|
+
- **Every slice is a COMPLETE pattern, never a half-slice.** A State Change is
|
|
63
|
+
`ui → command → event`; a State View is `event → read model → ui` (or `→ reaction`). So: a
|
|
64
|
+
command needs something that **triggers** it (the `ui` it's issued from, or the reaction in the
|
|
65
|
+
slice before it), its event needs a **reader**, and that read model needs a **consumer** of its
|
|
66
|
+
own. A command nothing points at is a write nobody can start; an event nothing projects is a
|
|
67
|
+
write nobody can see; a read model nothing displays is information dropped on the floor. Write
|
|
68
|
+
the whole chain together rather than sweeping up dangling ends later. Reactions don't count as
|
|
69
|
+
readers of *events* — they read views. **Every instance** of a repeated read model needs its own
|
|
70
|
+
consumer: if you repeat a view next to an event just to keep the arrow short, bring its screen
|
|
71
|
+
along, or don't add the instance.
|
|
72
|
+
- **Only six connections are legal:** `ui → command`, `command → event`, `event → read model`,
|
|
73
|
+
`read model → ui`, `read model → reaction`, `reaction → command`. Reaching for anything else —
|
|
74
|
+
above all `command → read model` (the CQRS violation) or `read model → command` — means the model
|
|
75
|
+
is missing an **element**, not an arrow.
|
|
54
76
|
- **Validate continuously.** Run `em validate` and fix errors/warnings as you go (see DSL ref).
|
|
55
77
|
- **Save state at the end of every session** so work resumes cleanly.
|
|
56
78
|
|
|
@@ -78,13 +100,19 @@ validation rules) before doing real work — they are the source of truth. Templ
|
|
|
78
100
|
README.md # overview + slice index (from templates/model-readme.md)
|
|
79
101
|
.event-modeling.md # resumable state (from templates/state.md)
|
|
80
102
|
slices/<slice-name>.md # one rich slice doc per slice (from templates/slice.md)
|
|
103
|
+
conformance/<date>-report.md
|
|
104
|
+
# conform-phase reports (from templates/conformance-report.md)
|
|
105
|
+
<model-name>-asis.em # conform-phase scratch model, regenerated per run — git-ignore this
|
|
81
106
|
```
|
|
82
107
|
|
|
83
108
|
When creating a new model, scaffold the directory and copy the templates in (filling the
|
|
84
109
|
placeholders). Copy `live.html` **verbatim** — it takes the SVG name from its URL query
|
|
85
110
|
(`live.html?svg=<model-name>.svg`), so there's nothing to edit. You may use `em init` for a
|
|
86
111
|
starter `.em`, but usually you'll build it up from the discovery conversation instead. Fill
|
|
87
|
-
template placeholders — never leave `{{...}}` in delivered files.
|
|
112
|
+
template placeholders — never leave `{{...}}` in delivered files. The `conformance/` directory
|
|
113
|
+
and `<model-name>-asis.em` only appear once the `conform` phase runs; add the pattern `*-asis.em`
|
|
114
|
+
to the repository's `.gitignore` the first time one is created (see `reference/conform.md`) —
|
|
115
|
+
it's scratch, never committed.
|
|
88
116
|
|
|
89
117
|
---
|
|
90
118
|
|
|
@@ -109,12 +137,20 @@ phase applies to field tables, below.
|
|
|
109
137
|
**callers** at each step instead; they become the inbound write translations and read
|
|
110
138
|
translations (see `reference/methodology.md`).
|
|
111
139
|
3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
|
|
112
|
-
Form `command → event` slices (State Change pattern).
|
|
140
|
+
Form `command → event` slices (State Change pattern). Every command needs a **trigger** in the
|
|
141
|
+
same breath: the screen the user issues it from (a `ui` in the slice), or — for the Automation
|
|
142
|
+
and Translation patterns — the reaction in the slice before it. A command nothing points at is
|
|
143
|
+
a write nobody can start. *"Who does this, and where are they when they do it?"*
|
|
113
144
|
4. **Outputs (step 4).** Identify the **read models / views** consumers need and wire them with
|
|
114
|
-
`from "Event"` (State View pattern)
|
|
145
|
+
`from "Event"` (State View pattern), each with the screen (or reaction) that consumes it — a
|
|
146
|
+
read model nothing displays is information dropped on the floor. In a **headless/API** model there is no UI — a read model is
|
|
115
147
|
consumed by an **external read translation** (the API query, triggers no command), and read
|
|
116
|
-
models are **repeated** in each slice where they're read
|
|
117
|
-
`reference/methodology.md`).
|
|
148
|
+
models are **repeated** in each slice where they're read (use `view X again` after the first) so
|
|
149
|
+
the timeline flows (see `reference/methodology.md`).
|
|
150
|
+
**Close the loop before leaving this step:** every event from step 3 must be read by at least one
|
|
151
|
+
read model. Walk the event list and ask, for each one, *"who looks at this, and what do they do
|
|
152
|
+
with it?"* An event with no answer is either missing its read model or shouldn't be recorded —
|
|
153
|
+
both are worth raising with the user rather than leaving dangling.
|
|
118
154
|
|
|
119
155
|
End of phase: write/refresh the `.em`, render it, update `.event-modeling.md` (steps done,
|
|
120
156
|
decisions, open questions) and `README.md`'s slice index. Tell the user they can stop here and
|
|
@@ -156,12 +192,17 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
|
|
|
156
192
|
event).
|
|
157
193
|
2. **Elaborate scenarios — first pass (step 6).** For each slice, capture the happy-path
|
|
158
194
|
Given/When/Then and the obvious invariants as short notes. (The full spec is the `slice` phase.)
|
|
159
|
-
3. **Evaluate completeness (step 7).** Walk the model: every
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
195
|
+
3. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
|
|
196
|
+
half-slice — **every command has something that triggers it**, every command emits an event,
|
|
197
|
+
**every event is read by a read model**, **every read model has a consumer**, every view has a
|
|
198
|
+
source, every UI is reachable, every connection is
|
|
199
|
+
one of the six legal pairs, and automations **and translations** split correctly (each reaction
|
|
200
|
+
triggers a command — none wired straight to an event). Run `em validate` and resolve all errors
|
|
201
|
+
and warnings — but check the reaction→command→event split by hand, since `em validate` does
|
|
202
|
+
**not** flag a translation/automation that emits an event directly (see `reference/em-dsl.md`).
|
|
203
|
+
For an unread event, don't just bolt on a view to silence the warning: ask the user who looks at
|
|
204
|
+
this fact and what they do with it. The honest answers are "here's the read model we missed" or
|
|
205
|
+
"nobody — so why are we recording it", and both improve the model.
|
|
165
206
|
|
|
166
207
|
End of phase: render, update state, suggest `/event-modeling slice` to write implementation specs.
|
|
167
208
|
|
|
@@ -173,7 +214,9 @@ timeline). Check `README.md`'s slice index for what's already done.
|
|
|
173
214
|
This is also where **branch / unhappy-path events** are discovered and added to the model — as a
|
|
174
215
|
slice's alternate/error flows surface (a rejection, removal, cancellation, decline, expiry), add
|
|
175
216
|
the corresponding event/slice to the `.em` and re-render. The happy-path spine from earlier
|
|
176
|
-
phases is the starting point, not the finished event set.
|
|
217
|
+
phases is the starting point, not the finished event set. **Every new event needs its reader too**
|
|
218
|
+
— a rejection or cancellation that nothing projects will warn, and usually the missing piece is
|
|
219
|
+
real (someone has to see that the request was declined).
|
|
177
220
|
|
|
178
221
|
For each slice:
|
|
179
222
|
1. Hold a Socratic deep-dive to fill every section of `templates/slice.md`: intent, trigger/actor,
|
|
@@ -194,6 +237,29 @@ For each slice:
|
|
|
194
237
|
`ready-to-implement`, later `implemented` once shipped, with `Implemented in:` filled in).
|
|
195
238
|
5. Re-render and `em validate`.
|
|
196
239
|
|
|
240
|
+
## Phase: `conform` — drift check against the codebase
|
|
241
|
+
|
|
242
|
+
Goal: check a **ratified** model (and its slice docs) against the codebase that implements it,
|
|
243
|
+
and report where they've drifted — advisory only, never a gate, never an unprompted edit.
|
|
244
|
+
|
|
245
|
+
**Read `reference/conform.md` before doing any conform work** — it carries the full flow
|
|
246
|
+
(scope, evidence-first verification, `em diff --json`, classification, report) and the
|
|
247
|
+
stance guardrails (evidence-first, uncertainty is never drift, propose-don't-edit). It reuses
|
|
248
|
+
`extract`'s sourcing/mode rules for reading the target codebase rather than duplicating them.
|
|
249
|
+
|
|
250
|
+
In short: for each in-scope slice, gather code evidence *before* comparing to the model or doc;
|
|
251
|
+
write the as-is picture into a scratch model (`<model-name>-asis.em`, reusing the canonical
|
|
252
|
+
model's names wherever the code matches them); run `em diff <model-name>.em
|
|
253
|
+
<model-name>-asis.em --json` and let `em` decide the structural deltas; classify every finding
|
|
254
|
+
(real drift / model gap / internal inconsistency / uncertainty) with cited evidence; write
|
|
255
|
+
`conformance/<date>-report.md` with proposed `issue "conformance: …"` red notes; apply only the
|
|
256
|
+
proposals the user ratifies, then re-render and validate; update the state file's
|
|
257
|
+
`Last conformance:` marker.
|
|
258
|
+
|
|
259
|
+
End of phase: state file's `Last conformance:` marker updated, Decisions log entry if any
|
|
260
|
+
proposals were applied. Conform doesn't chain to another phase — it's a recurring loop, run
|
|
261
|
+
again whenever the codebase has moved.
|
|
262
|
+
|
|
197
263
|
## Phase: `watch` — live team view
|
|
198
264
|
|
|
199
265
|
**Recommended (instant push, no polling):** start the watcher with `--serve` in the background:
|
|
@@ -210,8 +276,19 @@ editing of `live.html` is needed — the `?svg=` query picks the model.
|
|
|
210
276
|
## Phase: `validate`
|
|
211
277
|
|
|
212
278
|
Run `em validate <model-name>.em` and walk through each diagnostic with the user, explaining the
|
|
213
|
-
rule (see `reference/em-dsl.md`) and proposing the fix
|
|
214
|
-
|
|
279
|
+
rule (see `reference/em-dsl.md`) and proposing the fix. Apply fixes on agreement. Common ones:
|
|
280
|
+
|
|
281
|
+
| Diagnostic | Fix |
|
|
282
|
+
|---|---|
|
|
283
|
+
| automation/translation shares a slice with a command | Split the command into the next slice |
|
|
284
|
+
| **command has nothing that triggers it** | Add the `ui` it's issued from — or, if the system issues it, the reaction in the slice before it. Ask *"who does this, and where are they when they do it?"* |
|
|
285
|
+
| **read model has no consumer** | Add the screen that displays it, or the reaction that watches it. If it's a repeat added only to shorten an arrow and nothing looks at it there, drop the instance |
|
|
286
|
+
| read model has no source | Add the missing `from "Event"` |
|
|
287
|
+
| command produces no event | Give the command its event, or drop the command |
|
|
288
|
+
| **event is not read by any read model** | Add the read slice that projects it — but ask *who looks at this and what do they do with it* first. "Nobody" is a real answer, and then the question is why it's recorded at all |
|
|
289
|
+
| **illegal `arrow` kind pair** | Don't reroute it — the model is missing an element. `command → view` needs the event between them; `view → command` needs a reaction. Between two instances of one read model, just delete the arrow: the repeat needs no connection |
|
|
290
|
+
| `view X again` with no earlier declaration | Declare the view plainly the first time it appears |
|
|
291
|
+
| event feeding an earlier view instance | Add a `view X again` where the event lands and move the source there |
|
|
215
292
|
|
|
216
293
|
For anything genuinely unresolved rather than a rule violation, prefer `issue "text"` on the
|
|
217
294
|
relevant element over a `# TBD` comment — it shows up as a red marker on the rendered diagram and
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Conform Phase — Drift Detection Against the Codebase
|
|
2
|
+
|
|
3
|
+
Read this **before doing any conform work**. It governs the `conform` phase: checking a
|
|
4
|
+
**ratified** model (and its slice docs) against the codebase that is supposed to implement it,
|
|
5
|
+
and reporting where they've drifted apart. Conform reuses `extract`'s sourcing/mode rules
|
|
6
|
+
(`reference/extract.md`) for reading the target codebase rather than duplicating them — the
|
|
7
|
+
difference is what you do with what you find: extract *builds* a model from the code; conform
|
|
8
|
+
*compares* code evidence against a model that already exists.
|
|
9
|
+
|
|
10
|
+
## Stance — advisory-only, evidence-first
|
|
11
|
+
|
|
12
|
+
- **Advisory-only.** Conform never gates anything and never edits the canonical model or a
|
|
13
|
+
slice doc on its own initiative. It proposes; the user ratifies. There is no such thing as
|
|
14
|
+
an auto-applied conformance fix.
|
|
15
|
+
- **Evidence-first, not model-first.** For every slice you check, gather code evidence —
|
|
16
|
+
handler/event/projection/endpoint sites, invariant-enforcement sites, scenario-test sites —
|
|
17
|
+
**before** you compare anything to the model or doc. Read the doc and model first to know
|
|
18
|
+
*what to look for*, but write down what the code actually shows before you judge whether it
|
|
19
|
+
matches. Anchoring bias is the failure mode this rule exists to block: "the model says X" is
|
|
20
|
+
never allowed to substitute for "the code shows X at `path/to/File.kt:42`".
|
|
21
|
+
- **Uncertainty is never drift.** If you can't find evidence either way — the code path is
|
|
22
|
+
unclear, the test is ambiguous, you ran out of budget — that's an **uncertainty**, reported
|
|
23
|
+
in its own section, never folded into a drift or gap finding. A false positive here is worse
|
|
24
|
+
than a missed one; it's what burns the loop's credibility.
|
|
25
|
+
- **Anchored, not blind.** You read the canonical model's vocabulary before walking the code,
|
|
26
|
+
and you reuse its slice/element names wherever the code genuinely matches them. This trades
|
|
27
|
+
some independence for precision: naming a scratch model's elements from scratch (blind
|
|
28
|
+
extraction) would report wholesale slice add/remove noise from naming alone the moment your
|
|
29
|
+
synonym for an existing element doesn't match the model's — the exact false-drift failure
|
|
30
|
+
mode this phase exists to avoid. The guardrail against anchoring bias is the evidence-first
|
|
31
|
+
rule above, not independence from the model's vocabulary.
|
|
32
|
+
|
|
33
|
+
## Preconditions
|
|
34
|
+
|
|
35
|
+
1. A canonical `.em` model with a state file (`.event-modeling.md`), reachable the same way the
|
|
36
|
+
SKILL.md preconditions locate it.
|
|
37
|
+
2. The model should be ratified for the slices you're about to check — `slice` docs at
|
|
38
|
+
`implemented` (or the status the user says corresponds to shipped code) for whichever slices
|
|
39
|
+
are in scope. Checking a `draft` or `ready-to-implement` slice against code is a wasted walk;
|
|
40
|
+
tell the user and skip it rather than reporting "drift" against a doc nobody built to yet.
|
|
41
|
+
3. The codebase to check: the state file's **Existing system refs** (populated during
|
|
42
|
+
`extract`, if this model came from one) or ask the user for the repo path if not set. Record
|
|
43
|
+
it if missing. In a headless/scheduled run with no user to ask, default to the repository
|
|
44
|
+
the model lives in (if it contains the implementation) and state that assumption in the
|
|
45
|
+
report's run metadata.
|
|
46
|
+
4. Confirm `em --version` works (same check as every other phase) — you'll run `em diff`. The
|
|
47
|
+
phase needs `em diff --json`; if the flag errors as unknown, the installed `em` predates it
|
|
48
|
+
— say so and stop rather than falling back to eyeballing the two models.
|
|
49
|
+
|
|
50
|
+
## The three conformance surfaces
|
|
51
|
+
|
|
52
|
+
The model isn't just the `.em` — slice docs carry the detailed spec (field tables with rules,
|
|
53
|
+
named invariants, Given/When/Then scenarios). Conform checks all three surfaces that can drift:
|
|
54
|
+
|
|
55
|
+
| Surface | What drifts | Who checks | How |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| **Structural** — `.em` ↔ code | slices/elements/fields/flow | `em` (deterministic) | `em diff --json` on canonical vs. an as-is scratch model |
|
|
58
|
+
| **Spec** — slice doc ↔ code | field rules not enforced, named invariants (`INV-*`) without enforcement/test sites, GWT scenarios without test sites | you (judgment) | per-slice evidence walk: each checkable claim in the doc mapped to a code/test site by name (`INV-CHK-4` → its enforcement site + its test) |
|
|
59
|
+
| **Internal** — slice doc ↔ `.em` | the doc's header metadata (Pattern, Status) or its Command/Event/Read Model sections and field tables disagreeing with the model's own elements/fields | you (judgment, v1) | cross-check the doc's structured sections against the slice's model elements |
|
|
60
|
+
|
|
61
|
+
The internal surface is deterministically checkable in principle (structured doc sections +
|
|
62
|
+
`{ fields }` blocks) — promoting it to an `em validate` rule is a natural follow-up; file it as
|
|
63
|
+
its own issue rather than growing this phase's scope. For now it's agent judgment, same as spec.
|
|
64
|
+
|
|
65
|
+
**Internal inconsistency is its own class, not drift.** When a doc and the `.em` disagree with
|
|
66
|
+
each other, neither one is "the code" — you don't know which is stale. Report it as internal
|
|
67
|
+
inconsistency and let the user decide which side is right; don't guess by treating one as
|
|
68
|
+
authoritative.
|
|
69
|
+
|
|
70
|
+
## Flow
|
|
71
|
+
|
|
72
|
+
### 1. Scope
|
|
73
|
+
|
|
74
|
+
Default: **diff-scoped.** Read the state file's `Last conformance:` marker (date + target-repo
|
|
75
|
+
revision). If it's set, run `git diff --name-only <recorded-revision>..HEAD` from the target
|
|
76
|
+
repo's root to list the changed paths (`git log` alone prints commit subjects, not paths), then
|
|
77
|
+
map those paths to slices (via each slice doc's known code locations, `Implemented in:` links,
|
|
78
|
+
or a quick Grep if those are stale) — that's your in-scope set. If the marker reads `never`
|
|
79
|
+
(first run) or the user passes `--full`, scope is every `implemented` slice.
|
|
80
|
+
|
|
81
|
+
Cost containment here is cadence, not a trigger condition — diff-scoped-by-default is what
|
|
82
|
+
keeps a recurring conform run cheap; don't invent a smarter trigger.
|
|
83
|
+
|
|
84
|
+
### 2. Anchored, evidence-first verification (per in-scope slice)
|
|
85
|
+
|
|
86
|
+
For each slice in scope:
|
|
87
|
+
|
|
88
|
+
1. Read the slice's **doc** (`slices/<slice-name>.md`) alongside its elements in the `.em`.
|
|
89
|
+
This tells you the vocabulary to anchor on and the claims to check — not the answer.
|
|
90
|
+
2. Walk the code and **record evidence before comparing**:
|
|
91
|
+
- **Structural:** the handler, event class(es), projection(s), endpoint(s) that implement
|
|
92
|
+
this slice.
|
|
93
|
+
- **Spec:** for every named invariant (`INV-*`) in the doc, its enforcement site and its
|
|
94
|
+
test site (by name/path, both or neither — a half-found invariant is an uncertainty, not
|
|
95
|
+
a pass). Scenarios are budgeted: map Given/When/Then scenarios to individual test sites
|
|
96
|
+
when the in-scope slice count is small (roughly ≤5) or the user asks for that depth;
|
|
97
|
+
otherwise record slice-level scenario-test presence and say so in the report — per-scenario
|
|
98
|
+
mapping across a full model roughly doubles the walk. Where the doc doesn't name
|
|
99
|
+
invariants/scenarios individually, fall back to slice-level test presence (some vs. none)
|
|
100
|
+
rather than inventing IDs the doc never gave you.
|
|
101
|
+
- **Legitimate non-materialized views:** an automation's watched view may have no
|
|
102
|
+
materialized projection in code — a direct event handler with criteria-sourced state is
|
|
103
|
+
the same pattern expressed in the code idiom (extract.md's R5 reaction rules). That is
|
|
104
|
+
NOT structural drift; note the idiom in the evidence log instead of reporting the view
|
|
105
|
+
as missing.
|
|
106
|
+
3. Write what you found into the scratch model (`<model-name>-asis.em`, see below). **Seed the
|
|
107
|
+
scratch model as a copy of the canonical `.em` and replace only the in-scope slices** with
|
|
108
|
+
the as-is picture, leaving out-of-scope slices byte-identical. This is not an optimization:
|
|
109
|
+
`em diff` matches slices by name, so a scratch model containing *only* the in-scope slices
|
|
110
|
+
reports every other canonical slice as `slice-removed` plus one `element-removed` per
|
|
111
|
+
element — a phantom-removal flood on every diff-scoped run, which is the exact false drift
|
|
112
|
+
this phase exists to avoid. It also keeps the scratch model compilable (see step 3). Within
|
|
113
|
+
the slices you do rewrite, **reuse the canonical model's slice and element names wherever
|
|
114
|
+
the code genuinely matches them** — the canonical model is the vocabulary anchor. Give a
|
|
115
|
+
new name only to code behavior the model doesn't cover at all. Record the mapping decision
|
|
116
|
+
for every element you name this way (e.g. `CartItemAdded` code class ↔ `"Cart Item Added"`
|
|
117
|
+
model event) so it's auditable, not a silent judgment call. **Match the canonical model's field granularity:** declare `{ fields }`
|
|
118
|
+
in the scratch model only where the canonical model declares them (if the canonical model
|
|
119
|
+
puts fields on commands but not events, so does the scratch model — writing code-derived
|
|
120
|
+
event fields there would flood the diff with `field-added` noise). Schema claims at finer
|
|
121
|
+
granularity than the `.em` declares — e.g. event fields documented only in a slice doc's
|
|
122
|
+
table — are checked on the **spec surface** against the doc, not smuggled into the
|
|
123
|
+
structural diff.
|
|
124
|
+
4. Cross-check the doc's header metadata and Command/Event/Read Model sections against the
|
|
125
|
+
slice's actual `.em` elements and fields for the internal surface — this doesn't need code
|
|
126
|
+
evidence, just doc-vs-`.em` reading. Narrative sections (Intent, Open Questions, notes)
|
|
127
|
+
count too when the contradiction is unambiguous — a stale sentence that flatly contradicts
|
|
128
|
+
the `.em` is an internal inconsistency; a vague or interpretable one is not a finding.
|
|
129
|
+
|
|
130
|
+
Do this for every in-scope slice before moving on to diffing. The evidence you record here is
|
|
131
|
+
what every finding in the report will cite — file paths, not vibes.
|
|
132
|
+
|
|
133
|
+
### 3. Deterministic diff
|
|
134
|
+
|
|
135
|
+
First `em validate <model-name>-asis.em`. `em diff` refuses when either side has validation
|
|
136
|
+
*errors* (warnings are fine), so a scratch model that doesn't compile stops the run cold. The
|
|
137
|
+
usual cause is a slice that depends on one you rewrote incompletely — a `view X again` whose
|
|
138
|
+
first declaration you dropped, an automation `from` a read model that no longer exists, or an
|
|
139
|
+
`arrow` you wrote joining kinds the patterns don't connect. (An unread event is only a *warning*,
|
|
140
|
+
so it never blocks the diff — and in an as-is scratch model it's expected.)
|
|
141
|
+
Seeding from the canonical model (step 2) prevents this; if it still fails, **fix the scratch
|
|
142
|
+
model and report the failure as a tooling problem in the run metadata — never as drift.** A
|
|
143
|
+
model you couldn't compile is an uncertainty about your own scratch file, not a finding
|
|
144
|
+
against the codebase.
|
|
145
|
+
|
|
146
|
+
Then run `em diff <model-name>.em <model-name>-asis.em --json`. `em`, not you, decides what
|
|
147
|
+
structurally differs — don't re-derive by eye what the tool already computed. Parse the JSON
|
|
148
|
+
(`docs/cli.md`'s diff section documents the envelope: `diffSchemaVersion`, `counts`, `changes`,
|
|
149
|
+
`removals`, every optional `ChangeEntry` field explicit-`null` when unused) and use it as the
|
|
150
|
+
structural-surface findings directly.
|
|
151
|
+
|
|
152
|
+
### 4. Classify
|
|
153
|
+
|
|
154
|
+
Every finding — structural deltas from step 3, plus spec- and internal-surface findings from
|
|
155
|
+
step 2 — sorts into exactly one class:
|
|
156
|
+
|
|
157
|
+
- **Real drift** — code evidence contradicts the model or doc.
|
|
158
|
+
- **Model gap** — code evidence shows behavior neither the model nor the doc covers.
|
|
159
|
+
- **Internal inconsistency** — the doc and the `.em` disagree with each other (surface 3 only).
|
|
160
|
+
- **Extraction uncertainty** — no evidence either way. Never reported as drift or a gap.
|
|
161
|
+
|
|
162
|
+
Every classification must cite the evidence recorded in step 2 (or the `em diff` entry, for
|
|
163
|
+
structural findings) — a finding with no citation isn't ready to report.
|
|
164
|
+
|
|
165
|
+
### 5. Report + proposals
|
|
166
|
+
|
|
167
|
+
Write `conformance/<YYYY-MM-DD>-report.md` in the model directory, from
|
|
168
|
+
`templates/conformance-report.md`. For a real-drift or model-gap finding, propose a
|
|
169
|
+
ready-to-apply red note — `issue "conformance: <text>"` on the right element, written out in
|
|
170
|
+
the report exactly as it should be pasted into the `.em` — when a model-side marker is the
|
|
171
|
+
right fix; when the fix is purely doc wording (or the finding is an internal inconsistency
|
|
172
|
+
with no clear side to flag), omit the red note and say why instead. **You never edit the
|
|
173
|
+
canonical model or a slice doc unprompted** — walk the report with the user, apply only the
|
|
174
|
+
proposals they approve, then re-render and `em validate`.
|
|
175
|
+
|
|
176
|
+
End of run: update the state file's `Last conformance:` marker, exact format:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
- **Last conformance:** YYYY-MM-DD @ <target-repo revision> — report: conformance/YYYY-MM-DD-report.md
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The revision is the one you just diffed against, so the next run's scope starts from here —
|
|
183
|
+
step 1 parses this line, so keep the format exact. If any proposals were ratified and applied,
|
|
184
|
+
log a Decisions entry noting what changed and why.
|
|
185
|
+
|
|
186
|
+
## Conventions
|
|
187
|
+
|
|
188
|
+
- **Scratch model:** `<model-name>-asis.em`, written **next to** the canonical model (same
|
|
189
|
+
directory as `.event-modeling.md`). Always seeded as a copy of the canonical model with only
|
|
190
|
+
the in-scope slices rewritten (step 2), never built up from nothing. It's regenerated every
|
|
191
|
+
conform run — add the pattern `*-asis.em` to the repository's `.gitignore` (create the entry
|
|
192
|
+
if there isn't one) so it never gets committed.
|
|
193
|
+
- **Report location:** `conformance/<YYYY-MM-DD>-report.md` in the model directory. One file
|
|
194
|
+
per run; don't overwrite a prior date's report.
|
|
195
|
+
- **Red note wording:** `issue "conformance: <what's wrong, plainly>"` — plain enough that
|
|
196
|
+
whoever ratifies it later understands the claim without re-reading the report.
|
|
197
|
+
- **Naming/matching rules:** same past-tense-event / imperative-command / thing-shown-view
|
|
198
|
+
naming discipline as the rest of the skill (`methodology.md`'s Socratic stance) — you're
|
|
199
|
+
matching code to existing names here, not inventing new ones except for genuinely uncovered
|
|
200
|
+
behavior.
|
|
201
|
+
|
|
202
|
+
## Completion & handoff
|
|
203
|
+
|
|
204
|
+
Conform is complete when: every in-scope slice has been walked (step 2), the scratch model has
|
|
205
|
+
been diffed (step 3), every finding is classified with cited evidence (step 4), the report is
|
|
206
|
+
written (step 5), the state file's `Last conformance:` marker is updated, and the user has seen
|
|
207
|
+
the report and said which proposals (if any) to apply. Applied proposals get re-rendered and
|
|
208
|
+
re-validated before you call the run done.
|
|
209
|
+
|
|
210
|
+
Conform doesn't chain to another phase automatically — it's a recurring loop, not a step in
|
|
211
|
+
building the model. Suggest running it again next time the target codebase has moved, at
|
|
212
|
+
whatever cadence the user wants (see `docs/ci.md` for a scheduled-run recipe).
|
|
213
|
+
|
|
214
|
+
## Anti-patterns
|
|
215
|
+
|
|
216
|
+
- **Blind extraction** — deriving the as-is scratch model with no sight of the canonical
|
|
217
|
+
model's vocabulary. Loses the anchor and produces naming-driven false drift.
|
|
218
|
+
- **A scratch model holding only the in-scope slices** — `em diff` reads every omitted
|
|
219
|
+
canonical slice as removed, so a diff-scoped run drowns in phantom removals. Seed from the
|
|
220
|
+
canonical model and rewrite only the slices you walked.
|
|
221
|
+
- **Reporting uncertainty as drift** — "I couldn't find the enforcement site" is an
|
|
222
|
+
uncertainty, never a finding against the model.
|
|
223
|
+
- **Editing the model or a slice doc without ratification** — conform proposes; the user
|
|
224
|
+
applies. No exceptions, even for an "obviously correct" fix.
|
|
225
|
+
- **Treating scenario/test absence as a build failure** — a missing test site is a spec-surface
|
|
226
|
+
finding to report and classify, not a reason to stop the run or block anything. Conform is
|
|
227
|
+
advisory; there's no gate here.
|
|
228
|
+
- **Re-deriving what `em diff` already decided** — once you have the JSON, use it; don't
|
|
229
|
+
eyeball the two `.em` files yourself looking for structural differences the tool already
|
|
230
|
+
computed for you.
|
|
@@ -64,9 +64,10 @@ arrow From Element -> To Element # explicit cross-slice edge (overrides infer
|
|
|
64
64
|
- **`again`** (views only): `view <Name> again [from "Event", …]` declares a later instance of an
|
|
65
65
|
already-declared read model — the forward-only device for a view that evolves as later events
|
|
66
66
|
land. Instances are ONE logical view: the first declaration owns the `note` binding; each
|
|
67
|
-
instance's `from` lists only the NEW events landing at that point (not cumulative);
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
instance's `from` lists only the NEW events landing at that point (not cumulative); a reaction
|
|
68
|
+
(`from "View"`) reads the nearest instance at-or-before its own slice. Instances are NEVER
|
|
69
|
+
connected to one another — continuity is implied by the shared name, and the events reaching each
|
|
70
|
+
instance are what show the view changing. `again` with no earlier declaration is a validation error.
|
|
70
71
|
Use `again` (not a plain repeated `view` name) whenever the view is referenced by a
|
|
71
72
|
`from`/`arrow` — plain repeats are only warning-free while unreferenced.
|
|
72
73
|
- **`note "path.md"`** on ANY element links a markdown doc. Relative to the `.em` file. Renders as
|
|
@@ -106,7 +107,7 @@ slice "Open Orders" {
|
|
|
106
107
|
ui Order List @Customer
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
# 3. Automation: split across TWO slices
|
|
110
|
+
# 3. Automation: split across TWO slices (plus the read slice that consumes the event)
|
|
110
111
|
slice "Orders To Fulfill" {
|
|
111
112
|
view Orders To Fulfill from "Order Placed"
|
|
112
113
|
processor Fulfillment Service
|
|
@@ -115,6 +116,10 @@ slice "Ship Order" { # the triggered command goes in the NEXT slice
|
|
|
115
116
|
command Ship Order
|
|
116
117
|
event Order Shipped @Shipping
|
|
117
118
|
}
|
|
119
|
+
slice "Open Orders — shipped" {
|
|
120
|
+
view Open Orders again from "Order Shipped" # every event needs a reader
|
|
121
|
+
ui Order List @Customer # ...and every read model needs a consumer
|
|
122
|
+
}
|
|
118
123
|
|
|
119
124
|
# 4a. Translation (external trigger): external input -> translation -> command -> event
|
|
120
125
|
slice "Carrier Webhook" {
|
|
@@ -124,8 +129,17 @@ slice "Confirm Delivery" { # the triggered command goes in the NEXT s
|
|
|
124
129
|
command Confirm Delivery
|
|
125
130
|
event Delivery Confirmed @Shipping
|
|
126
131
|
}
|
|
132
|
+
slice "Open Orders — delivered" {
|
|
133
|
+
view Open Orders again from "Delivery Confirmed"
|
|
134
|
+
ui Order List @Customer
|
|
135
|
+
}
|
|
127
136
|
|
|
128
137
|
# 4b. Translation (internal trigger): read model -> translation -> command -> event
|
|
138
|
+
slice "Accept Quote" {
|
|
139
|
+
ui Quote Screen @Customer # every command needs a trigger: a ui, or a reaction before it
|
|
140
|
+
command Accept Quote
|
|
141
|
+
event Quote Accepted @Quote
|
|
142
|
+
}
|
|
129
143
|
slice "Quotes To Sync" {
|
|
130
144
|
view Accepted Quotes from "Quote Accepted"
|
|
131
145
|
translation CRM Sync # reacts to our own state via the read model in this slice
|
|
@@ -134,40 +148,66 @@ slice "Record Sync" {
|
|
|
134
148
|
command Record Crm Sync
|
|
135
149
|
event Quote Synced @Quote
|
|
136
150
|
}
|
|
151
|
+
slice "Accepted Quotes — synced" {
|
|
152
|
+
view Accepted Quotes again from "Quote Synced"
|
|
153
|
+
ui Sync Status @Customer
|
|
154
|
+
}
|
|
137
155
|
```
|
|
138
156
|
|
|
139
157
|
A `translation` (like a `processor`) is a **reaction**: it triggers a command and never carries an
|
|
140
158
|
`event` in its own slice. Same two-slice split as the Automation pattern above.
|
|
141
159
|
|
|
160
|
+
Note the read slice closing each pattern: **every event must be read by some read model**
|
|
161
|
+
(warning 4 below). A command slice is not finished until the slice that projects its event
|
|
162
|
+
exists. Reactions don't count — they read *views*, not events.
|
|
163
|
+
|
|
142
164
|
### Headless / API systems (no UI) & repeated read models
|
|
143
165
|
|
|
144
166
|
When the system is headless (no UI — clients call an API), drop `ui`/`persona` entirely:
|
|
145
167
|
|
|
168
|
+
A slice body is always multi-line — there is **no one-line `slice "X" { … }` form**, and writing
|
|
169
|
+
one is a parse error (`'slice' is not valid inside a slice`).
|
|
170
|
+
|
|
146
171
|
```em
|
|
147
172
|
# Write: external translation -> command -> event (name the inbound adapter for the caller/role)
|
|
148
|
-
slice "Create Quote (request)" {
|
|
149
|
-
|
|
173
|
+
slice "Create Quote (request)" {
|
|
174
|
+
translation SalesRep BU Create Quote
|
|
175
|
+
}
|
|
176
|
+
slice "Create Quote" {
|
|
177
|
+
command Create Quote
|
|
178
|
+
event QuoteCreated @Quote
|
|
179
|
+
}
|
|
150
180
|
|
|
151
|
-
# Read: event(s) -> read model -> READ translation (the external caller's API query, replaces UI)
|
|
152
|
-
slice
|
|
181
|
+
# Read: event(s) -> read model -> READ translation (the external caller's API query, replaces UI).
|
|
182
|
+
# This slice is also what makes QuoteCreated a *read* event — without it the model warns.
|
|
183
|
+
slice "Read Quote — created" {
|
|
184
|
+
view Quote from "QuoteCreated"
|
|
185
|
+
translation SalesRep BU Read Quote
|
|
186
|
+
}
|
|
153
187
|
```
|
|
154
188
|
|
|
155
189
|
- A **read translation triggers no command** (it returns data outbound) — it is the headless analog
|
|
156
190
|
of `view → ui`, *not* a reaction. Only *reaction* translations/processors trigger commands. A read
|
|
157
191
|
slice has no `command` and no `event`, so `em validate` stays quiet about it.
|
|
158
|
-
- **Repeat the read model** in every slice where it's read so the timeline flows left-to-right
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
192
|
+
- **Repeat the read model** in every slice where it's read so the timeline flows left-to-right, and
|
|
193
|
+
**prefer `view X again`** for every instance after the first (see Clauses). `again` instances are
|
|
194
|
+
exempt from the duplicate-name warning even when referenced, and each reference resolves to the
|
|
195
|
+
right instance — a plain repeat only stays warning-free while nothing references it by name, and
|
|
196
|
+
resolves to the *first* declaration when something does. **Wire each event to a read model exactly
|
|
197
|
+
once:** a repeated instance's `from` lists only the **new** events since the previous instance
|
|
198
|
+
(not cumulative), or the event draws a duplicate arrow to the same read model at every repeat. (An
|
|
199
|
+
event may still feed several *different* read models, once each.)
|
|
200
|
+
- **Instances are never joined to one another.** No arrow between two instances of one read model,
|
|
201
|
+
ever — an explicit one is a validation error. The repeat is a timeline device: continuity is
|
|
202
|
+
implied by the shared name, and the events arriving at each instance are what show it changing.
|
|
167
203
|
- **Keep arrows span-1: put each repeat right after its feeding event.** Place a read-model instance
|
|
168
|
-
immediately after the event that updates it, sourcing only that single adjacent event.
|
|
169
|
-
|
|
170
|
-
read→read
|
|
204
|
+
immediately after the event that updates it, sourcing only that single adjacent event. The
|
|
205
|
+
renderer routes a long arrow *around* intervening boxes rather than through them, so it no longer
|
|
206
|
+
reads as a forbidden read→read link — but distance is still a real problem: the arrowhead lands
|
|
207
|
+
columns away from the event that produced it, so **the write slice reads as dangling**, as if
|
|
208
|
+
nothing consumed its event. You have to trace a line across the diagram to see the connection.
|
|
209
|
+
Keep the read model adjacent to its event, and keep a sub-flow that detours into another context
|
|
210
|
+
together rather than parking it at the end of the model.
|
|
171
211
|
|
|
172
212
|
### Slice-ordering gotchas (edge inference)
|
|
173
213
|
|
|
@@ -197,20 +237,33 @@ slice "Read Quote — created" { view Quote from "QuoteCreated" translation S
|
|
|
197
237
|
there), a reaction reading a view before any instance of it exists, or an explicit backward
|
|
198
238
|
`arrow`.
|
|
199
239
|
6. **`again` without an earlier declaration** — declare the view plainly the first time it appears.
|
|
240
|
+
7. **Illegal connection** — an `arrow` joining kinds the patterns don't connect. Only
|
|
241
|
+
`ui -> command`, `command -> event`, `event -> view`, `view -> ui`, `view -> reaction`, and
|
|
242
|
+
`reaction -> command` are allowed. A command straight to a view is the CQRS violation (an event
|
|
243
|
+
has to sit between them); a view straight to a command needs a reaction between them; and two
|
|
244
|
+
instances of one view are never connected at all. Inferred edges are always legal, so this only
|
|
245
|
+
ever fires on a hand-written `arrow`.
|
|
200
246
|
|
|
201
247
|
**Warnings (should fix):**
|
|
202
248
|
1. **Automation/translation shares slice with its command** — both `automation`/`processor` and
|
|
203
249
|
`translation` are reactions; put the triggered command in the *next* slice.
|
|
204
|
-
2. **Command
|
|
205
|
-
|
|
206
|
-
4
|
|
207
|
-
|
|
250
|
+
2. **Command with no trigger** — nothing issues it. A command needs a `ui` in its slice, or a
|
|
251
|
+
reaction (automation/processor/saga/translation) in the slice *before* it. The input-side mirror
|
|
252
|
+
of (4): a command nothing points at is a write nobody can start.
|
|
253
|
+
3. **Command without event** — every command should record at least one event.
|
|
254
|
+
4. **Event nobody reads** — the mirror of (3): recording an event no read model projects is a
|
|
255
|
+
write with no reader. Follow every write slice with the read slice that consumes its event.
|
|
256
|
+
Counts as read when a `view` names it in `from` (any `again` instance will do), when a
|
|
257
|
+
fieldless-`from` view sits in its slice, or via an explicit `event -> view` arrow.
|
|
258
|
+
5. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
|
|
259
|
+
6. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
|
|
260
|
+
7. **Open issue** — an element carries `issue "text"`; resolve the question, then remove the
|
|
208
261
|
clause. `em validate --list-issues` prints just these; `--fail-on-issues` (opt-in) makes CI
|
|
209
262
|
fail while any remain open.
|
|
210
|
-
|
|
263
|
+
8. **View field with no source** — a `view` field whose name matches no field on any instance
|
|
211
264
|
of its source events. Only checked once BOTH the view and at least one source event declare
|
|
212
265
|
`{ fields }`.
|
|
213
|
-
|
|
266
|
+
9. **Event field not from a command** — an `event` field whose name matches no field on any
|
|
214
267
|
command in the same slice. Only checked once BOTH the event and at least one same-slice
|
|
215
268
|
command declare `{ fields }`. This is the payoff of the fields feature for slicing rigor:
|
|
216
269
|
once fields are written down, `em validate` checks that data flows forward consistently.
|
|
@@ -218,6 +271,20 @@ slice "Read Quote — created" { view Quote from "QuoteCreated" translation S
|
|
|
218
271
|
**Design rules that keep models valid:**
|
|
219
272
|
- One element per band per slice (multiple personas/contexts are fine — they're different rows).
|
|
220
273
|
- Every `command` slice includes its `event`. Every `view` has a `from` source.
|
|
274
|
+
- **Every `command` has a trigger.** A `ui` in its slice, or a reaction in the slice *before* it.
|
|
275
|
+
A command nothing points at is a write nobody can start.
|
|
276
|
+
- **Every `event` has a reader.** A command slice isn't finished until the read slice that projects
|
|
277
|
+
its event exists. Reactions don't count — they read views, not events. Pair each write slice with
|
|
278
|
+
its read slice as you go rather than sweeping up dangling events at the end.
|
|
279
|
+
- **Every `view` has a consumer.** A `ui` in its slice, a reaction watching it, or (headless) a read
|
|
280
|
+
translation. Every *instance* of a repeat, not just the last — a bare `view X again` slice is a
|
|
281
|
+
half-slice, not a State View.
|
|
282
|
+
- **Only six connections are legal**, and only these are ever inferred:
|
|
283
|
+
`ui → command`, `command → event`, `event → view`, `view → ui`, `view → reaction`,
|
|
284
|
+
`reaction → command`. Anything else in an explicit `arrow` is an error — above all
|
|
285
|
+
`command → view` (the CQRS violation: an event has to sit between them) and `view → command`
|
|
286
|
+
(a reaction has to sit between them). If you reach for an arrow the patterns don't allow, the
|
|
287
|
+
model is missing an element, not an arrow.
|
|
221
288
|
- Automations **and translations** are always two slices: the reaction (plus its read model, if
|
|
222
289
|
internally triggered) in one slice, the triggered `command` + its `event` in the next. A
|
|
223
290
|
translation/automation slice **never contains an `event`** — reactions trigger commands, not
|