@milehimikey/em 1.4.0 → 1.6.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.
Files changed (95) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +121 -30
  2. package/.claude/skills/event-modeling/reference/conform.md +36 -7
  3. package/.claude/skills/event-modeling/reference/em-dsl.md +210 -48
  4. package/.claude/skills/event-modeling/reference/extract.md +24 -7
  5. package/.claude/skills/event-modeling/reference/methodology.md +129 -32
  6. package/.claude/skills/event-modeling/templates/conformance-report.md +9 -7
  7. package/.claude/skills/event-modeling/templates/model-readme.md +9 -0
  8. package/.claude/skills/event-modeling/templates/slice.md +20 -1
  9. package/.claude/skills/event-modeling/templates/state.md +8 -0
  10. package/README.md +1 -0
  11. package/dist/catalog/build.d.ts +33 -0
  12. package/dist/catalog/build.js +117 -0
  13. package/dist/catalog/build.js.map +1 -0
  14. package/dist/catalog/classify.d.ts +14 -0
  15. package/dist/catalog/classify.js +44 -0
  16. package/dist/catalog/classify.js.map +1 -0
  17. package/dist/catalog/pages.d.ts +45 -0
  18. package/dist/catalog/pages.js +174 -0
  19. package/dist/catalog/pages.js.map +1 -0
  20. package/dist/catalog/sliceDoc.d.ts +10 -0
  21. package/dist/catalog/sliceDoc.js +20 -0
  22. package/dist/catalog/sliceDoc.js.map +1 -0
  23. package/dist/cli/catalog-inputs.d.ts +16 -0
  24. package/dist/cli/catalog-inputs.js +18 -0
  25. package/dist/cli/catalog-inputs.js.map +1 -0
  26. package/dist/cli/glossary-inputs.d.ts +15 -0
  27. package/dist/cli/glossary-inputs.js +17 -0
  28. package/dist/cli/glossary-inputs.js.map +1 -0
  29. package/dist/cli/render-inputs.d.ts +27 -0
  30. package/dist/cli/render-inputs.js +41 -0
  31. package/dist/cli/render-inputs.js.map +1 -0
  32. package/dist/cli.js +188 -10
  33. package/dist/cli.js.map +1 -1
  34. package/dist/emit/diffJson.d.ts +1 -1
  35. package/dist/emit/diffJson.js +8 -1
  36. package/dist/emit/diffJson.js.map +1 -1
  37. package/dist/emit/dot.d.ts +7 -0
  38. package/dist/emit/dot.js +10 -4
  39. package/dist/emit/dot.js.map +1 -1
  40. package/dist/emit/glossaryJson.d.ts +13 -0
  41. package/dist/emit/glossaryJson.js +36 -0
  42. package/dist/emit/glossaryJson.js.map +1 -0
  43. package/dist/emit/json.d.ts +16 -2
  44. package/dist/emit/json.js +57 -6
  45. package/dist/emit/json.js.map +1 -1
  46. package/dist/emit/theme.d.ts +3 -0
  47. package/dist/emit/theme.js +19 -0
  48. package/dist/emit/theme.js.map +1 -1
  49. package/dist/model/diff.d.ts +36 -1
  50. package/dist/model/diff.js +198 -35
  51. package/dist/model/diff.js.map +1 -1
  52. package/dist/model/edges.d.ts +12 -1
  53. package/dist/model/edges.js +26 -24
  54. package/dist/model/edges.js.map +1 -1
  55. package/dist/model/glossary.d.ts +68 -0
  56. package/dist/model/glossary.js +138 -0
  57. package/dist/model/glossary.js.map +1 -0
  58. package/dist/model/model.d.ts +35 -0
  59. package/dist/model/model.js +41 -0
  60. package/dist/model/model.js.map +1 -1
  61. package/dist/model/validate.js +252 -1
  62. package/dist/model/validate.js.map +1 -1
  63. package/dist/parser/ast.d.ts +20 -0
  64. package/dist/parser/parser.js +158 -29
  65. package/dist/parser/parser.js.map +1 -1
  66. package/dist/render/drawEdges.d.ts +3 -1
  67. package/dist/render/drawEdges.js +119 -7
  68. package/dist/render/drawEdges.js.map +1 -1
  69. package/dist/render/drawNotes.d.ts +7 -3
  70. package/dist/render/drawNotes.js +75 -28
  71. package/dist/render/drawNotes.js.map +1 -1
  72. package/dist/render/render.d.ts +20 -1
  73. package/dist/render/render.js +57 -17
  74. package/dist/render/render.js.map +1 -1
  75. package/dist/render/serve.js +155 -0
  76. package/dist/render/serve.js.map +1 -1
  77. package/dist/render/sliceDiagram.d.ts +11 -0
  78. package/dist/render/sliceDiagram.js +198 -0
  79. package/dist/render/sliceDiagram.js.map +1 -0
  80. package/dist/render/sliceOverlay.d.ts +21 -0
  81. package/dist/render/sliceOverlay.js +111 -0
  82. package/dist/render/sliceOverlay.js.map +1 -0
  83. package/dist/render/sliceStatus.d.ts +5 -0
  84. package/dist/render/sliceStatus.js +26 -0
  85. package/dist/render/sliceStatus.js.map +1 -0
  86. package/dist/render/statusOverlay.d.ts +16 -0
  87. package/dist/render/statusOverlay.js +110 -0
  88. package/dist/render/statusOverlay.js.map +1 -0
  89. package/dist/render/svgGeometry.d.ts +43 -0
  90. package/dist/render/svgGeometry.js +69 -3
  91. package/dist/render/svgGeometry.js.map +1 -1
  92. package/dist/render/watch.d.ts +1 -1
  93. package/dist/render/watch.js +5 -2
  94. package/dist/render/watch.js.map +1 -1
  95. package/package.json +3 -2
@@ -42,19 +42,46 @@ validation rules) before doing real work — they are the source of truth. Templ
42
42
  and show the user what changed. Encourage running the live view (`watch`) so a team can follow.
43
43
  - **Keep the `.em` structural; put depth in `note` docs.** The diagram holds flow; invariants,
44
44
  fields, and scenarios live in `slices/*.md` linked via `note "slices/<name>.md"`.
45
- - **No UI in headless/API models.** If the system is headless (clients call an API, not screens),
46
- use no `ui`/`persona`: writes are `external translation → command → event` (name the inbound
47
- adapter for the caller/role); reads are `read model → read translation` (an API query that
48
- triggers **no** command — the analog of `view → ui`). **Repeat read models** across slices so the
49
- timeline flows left-to-right — put each repeat **right after the event that feeds it**, sourcing
45
+ - **Headless/API models still use `ui`.** If the system is headless (clients call an API, not
46
+ screens), declare a persona per external caller/role (e.g. `IntegratorAPI`) and treat its `ui`
47
+ boxes as API calls, not screens: writes are `ui → command → event` and reads are
48
+ `read model → ui`, exactly like any other State Change/State View slice — no separate trigger
49
+ slice. `translation` stays reserved for genuine reactions and real external-system boundaries,
50
+ not a synchronous request/response call. Internal-only commands/views with no public route carry
51
+ no `ui` at all — they follow the ordinary Automation pattern instead. **Repeat read models**
52
+ across slices so the timeline flows left-to-right — put each repeat **right after the event that feeds it**, sourcing
50
53
  only that one adjacent event, so every arrow is short (a read model far from its source events
51
- draws long arrows that look like a forbidden read→read link). Repeats render cleanly — em only
52
- warns on a duplicate name that's *referenced* by `from`/`arrow`. Slice order matters: a reaction
53
- must be directly followed by its command slice, and a read slice must not be immediately
54
+ draws a long arrow whose head lands columns away, making the *write* slice read as dangling —
55
+ keep a sub-flow that detours into another context together, not parked at the end of the model).
56
+ Declare every instance after the first with
57
+ **`view X again from "..."`** — `again` instances are exempt from the duplicate-name warning even
58
+ when referenced, and each reference resolves to the right instance. Slice order matters: a
59
+ reaction must be directly followed by its command slice, and a read slice must not be immediately
54
60
  followed by a command slice (see `em-dsl.md`).
55
61
  See `reference/methodology.md` (State View) and `reference/em-dsl.md`.
62
+ - **Never connect two instances of one read model.** The repeat is a timeline device, not a flow:
63
+ continuity is implied by the shared name, and the events arriving at each instance are what show
64
+ it changing. An arrow between instances says the read model feeds itself, and is an error.
65
+ - **Every slice is a COMPLETE pattern, never a half-slice.** A State Change is
66
+ `ui → command → event`; a State View is `event → read model → ui` (or `→ reaction`). So: a
67
+ command needs something that **triggers** it (the `ui` it's issued from, or the reaction in the
68
+ slice before it), its event needs a **reader**, and that read model needs a **consumer** of its
69
+ own. A command nothing points at is a write nobody can start; an event nothing projects is a
70
+ write nobody can see; a read model nothing displays is information dropped on the floor. Write
71
+ the whole chain together rather than sweeping up dangling ends later. Reactions don't count as
72
+ readers of *events* — they read views. **Every instance** of a repeated read model needs its own
73
+ consumer: if you repeat a view next to an event just to keep the arrow short, bring its screen
74
+ along, or don't add the instance.
75
+ - **Only six connections are legal:** `ui → command`, `command → event`, `event → read model`,
76
+ `read model → ui`, `read model → reaction`, `reaction → command`. Reaching for anything else —
77
+ above all `command → read model` (the CQRS violation) or `read model → command` — means the model
78
+ is missing an **element**, not an arrow.
56
79
  - **Validate continuously.** Run `em validate` and fix errors/warnings as you go (see DSL ref).
57
- - **Save state at the end of every session** so work resumes cleanly.
80
+ - **Save state at the end of every session** so work resumes cleanly. Also append one line to
81
+ the Usage log: the phase(s) touched and the `em validate` diagnostic *categories* that fired
82
+ (rule name only, e.g. "read model nothing consumes" — never the full message or domain
83
+ content). This is the team's only usage signal today (`docs/usage-data.md`) — keep it cheap
84
+ and habitual, not a task to skip.
58
85
 
59
86
  ## Preconditions (run first)
60
87
 
@@ -113,16 +140,24 @@ phase applies to field tables, below.
113
140
  they aren't re-proposed later. Capture the survivors as a flat list.
114
141
  2. **Plot / storyboard (step 2).** Order the events into the narrative. Identify **personas**
115
142
  (actors) and the **UI** screens at each step. Add `persona` declarations and `ui` elements.
116
- In a **headless/API** model there are no personas or screens — identify the external
117
- **callers** at each step instead; they become the inbound write translations and read
118
- translations (see `reference/methodology.md`).
143
+ In a **headless/API** model there are no human personas or screens — identify the external
144
+ **callers** at each step instead; declare one persona per caller/role, and its `ui` boxes are
145
+ the API calls (see `reference/methodology.md`).
119
146
  3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
120
- Form `command → event` slices (State Change pattern).
147
+ Form `command → event` slices (State Change pattern). Every command needs a **trigger** in the
148
+ same breath: the screen the user issues it from (a `ui` in the slice), or — for the Automation
149
+ and Translation patterns — the reaction in the slice before it. A command nothing points at is
150
+ a write nobody can start. *"Who does this, and where are they when they do it?"*
121
151
  4. **Outputs (step 4).** Identify the **read models / views** consumers need and wire them with
122
- `from "Event"` (State View pattern). In a **headless/API** model there is no UI — a read model is
123
- consumed by an **external read translation** (the API query, triggers no command), and read
124
- models are **repeated** in each slice where they're read so the timeline flows (see
125
- `reference/methodology.md`).
152
+ `from "Event"` (State View pattern), each with the screen (or reaction) that consumes it — a
153
+ read model nothing displays is information dropped on the floor. In a **headless/API** model
154
+ the consumer is the caller persona's `ui` (the API query) — same as any screen. Read models are
155
+ **repeated** in each slice where they're read (use `view X again` after the first, each instance
156
+ with its own consumer) so the timeline flows (see `reference/methodology.md`).
157
+ **Close the loop before leaving this step:** every event from step 3 must be read by at least one
158
+ read model. Walk the event list and ask, for each one, *"who looks at this, and what do they do
159
+ with it?"* An event with no answer is either missing its read model or shouldn't be recorded —
160
+ both are worth raising with the user rather than leaving dangling.
126
161
 
127
162
  End of phase: write/refresh the `.em`, render it, update `.event-modeling.md` (steps done,
128
163
  decisions, open questions) and `README.md`'s slice index. Tell the user they can stop here and
@@ -164,12 +199,17 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
164
199
  event).
165
200
  2. **Elaborate scenarios — first pass (step 6).** For each slice, capture the happy-path
166
201
  Given/When/Then and the obvious invariants as short notes. (The full spec is the `slice` phase.)
167
- 3. **Evaluate completeness (step 7).** Walk the model: every command emits an event, every view
168
- has a source, no orphan events, every UI is reachable, automations **and translations** split
169
- correctly (each reaction triggers a command — none wired straight to an event). Run
170
- `em validate` and resolve all errors and warnings — but check the reaction→command→event split
171
- by hand, since `em validate` does **not** flag a translation/automation that emits an event
172
- directly (see `reference/em-dsl.md`).
202
+ 3. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
203
+ half-slice — **every command has something that triggers it**, every command emits an event,
204
+ **every event is read by a read model**, **every read model has a consumer**, every view has a
205
+ source, every UI is reachable, every connection is
206
+ one of the six legal pairs, and automations **and translations** split correctly (each reaction
207
+ triggers a command — none wired straight to an event). Run `em validate` and resolve all errors
208
+ and warnings — but check the reaction→command→event split by hand, since `em validate` does
209
+ **not** flag a translation/automation that emits an event directly (see `reference/em-dsl.md`).
210
+ For an unread event, don't just bolt on a view to silence the warning: ask the user who looks at
211
+ this fact and what they do with it. The honest answers are "here's the read model we missed" or
212
+ "nobody — so why are we recording it", and both improve the model.
173
213
 
174
214
  End of phase: render, update state, suggest `/event-modeling slice` to write implementation specs.
175
215
 
@@ -181,7 +221,9 @@ timeline). Check `README.md`'s slice index for what's already done.
181
221
  This is also where **branch / unhappy-path events** are discovered and added to the model — as a
182
222
  slice's alternate/error flows surface (a rejection, removal, cancellation, decline, expiry), add
183
223
  the corresponding event/slice to the `.em` and re-render. The happy-path spine from earlier
184
- phases is the starting point, not the finished event set.
224
+ phases is the starting point, not the finished event set. **Every new event needs its reader too**
225
+ — a rejection or cancellation that nothing projects will warn, and usually the missing piece is
226
+ real (someone has to see that the request was declined).
185
227
 
186
228
  For each slice:
187
229
  1. Hold a Socratic deep-dive to fill every section of `templates/slice.md`: intent, trigger/actor,
@@ -195,12 +237,16 @@ For each slice:
195
237
  already defined elsewhere.
196
238
  2. Write the doc to `slices/<slice-name>.md` (kebab-case the slice name). Record the originating
197
239
  need (ticket/conversation link) in the Intent section when one exists.
198
- 3. Wire it into the `.em`: add `note "slices/<slice-name>.md"` to the slice's primary element
240
+ 3. Render the slice's own diagram: `em render <model>.em --slice "<slice name>" -o
241
+ slices/<slice-name>.svg` (kebab-case, matching the doc's filename) — redraws just this slice
242
+ in its own canonical pattern shape, and add `![Diagram](./<slice-name>.svg)` near the top of
243
+ the doc, right after the metadata bullet list.
244
+ 4. Wire it into the `.em`: add `note "slices/<slice-name>.md"` to the slice's primary element
199
245
  (the command for State Change, the view for State View, the processor for Automation, the
200
246
  translation for Translation).
201
- 4. Update `README.md`'s slice index — the one canonical slice table (`draft` →
247
+ 5. Update `README.md`'s slice index — the one canonical slice table (`draft` →
202
248
  `ready-to-implement`, later `implemented` once shipped, with `Implemented in:` filled in).
203
- 5. Re-render and `em validate`.
249
+ 6. Re-render and `em validate`.
204
250
 
205
251
  ## Phase: `conform` — drift check against the codebase
206
252
 
@@ -238,11 +284,53 @@ appear the moment you save, with no idle churn between edits.
238
284
  `live.html?svg=<model-name>.svg` in a browser. It polls every ~2s and reloads without flicker. No
239
285
  editing of `live.html` is needed — the `?svg=` query picks the model.
240
286
 
287
+ ## Phase: `review` — stakeholder walkthrough
288
+
289
+ Goal: step a real stakeholder review session through the model slice by slice, one slice
290
+ spotlighted at a time, with any open questions the room raises captured live as `issue "..."`
291
+ red notes.
292
+
293
+ **Trigger: a real stakeholder review session is on the calendar — a scheduled use, not
294
+ speculation.** Don't propose this phase proactively; it exists for facilitated reviews with
295
+ non-engineer stakeholders in the room, not routine model editing.
296
+
297
+ Start the same live server as `watch`: `em watch <model-name>.em -o <model-name>.svg --serve`
298
+ (run_in_background). Open the printed URL, click **Review mode** in the header, and share the
299
+ screen. Use Prev/Next (or the left/right arrow keys) to step through slices in declaration
300
+ order — each one pans/zooms into view with everything else dimmed, so the room's attention
301
+ tracks one slice at a time.
302
+
303
+ **Live capture:** when a stakeholder raises something unresolved, add `issue "..."` to the
304
+ relevant element in the `.em` file and save — exactly the same mechanism as any other issue
305
+ (see `validate` below). The browser updates over the existing SSE push within moments, without
306
+ losing the current slice or resetting review mode.
307
+
308
+ Wrap-up: run `em validate --list-issues` to sweep everything captured during the session and
309
+ walk each one with the user, same as any open issue. Update the state file's Participants
310
+ section with who attended, and set a `Last stakeholder review:` marker (mirrors `Last
311
+ conformance:`).
312
+
313
+ End of phase: state file's `Last stakeholder review:` marker updated, every issue captured
314
+ live triaged (resolved on the spot, moved to Open questions / parking lot, or left open on
315
+ purpose). Review doesn't chain to another phase — it's a recurring, scheduled activity like
316
+ `conform`, not a step in the discover → model → slice sequence.
317
+
241
318
  ## Phase: `validate`
242
319
 
243
320
  Run `em validate <model-name>.em` and walk through each diagnostic with the user, explaining the
244
- rule (see `reference/em-dsl.md`) and proposing the fix (e.g. split an automation's command into
245
- the next slice, add a missing `from`, give a command its event). Apply fixes on agreement.
321
+ rule (see `reference/em-dsl.md`) and proposing the fix. Apply fixes on agreement. Common ones:
322
+
323
+ | Diagnostic | Fix |
324
+ |---|---|
325
+ | automation/translation shares a slice with a command | Split the command into the next slice |
326
+ | **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?"* |
327
+ | **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 |
328
+ | read model has no source | Add the missing `from "Event"` |
329
+ | command produces no event | Give the command its event, or drop the command |
330
+ | **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 |
331
+ | **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 |
332
+ | `view X again` with no earlier declaration | Declare the view plainly the first time it appears |
333
+ | event feeding an earlier view instance | Add a `view X again` where the event lands and move the source there |
246
334
 
247
335
  For anything genuinely unresolved rather than a rule violation, prefer `issue "text"` on the
248
336
  relevant element over a `# TBD` comment — it shows up as a red marker on the rendered diagram and
@@ -264,9 +352,12 @@ em init <name>.em # optional starter scaffold
264
352
  em validate <name>.em # check rules; exit 0 if clean/warnings only
265
353
  em render <name>.em -o <name>.svg # render (svg/png/pdf by extension)
266
354
  em render <name>.em --emit-dot # inspect generated Graphviz DOT
355
+ em render <name>.em --slice "<slice-name>" -o slices/<slug>.svg # this slice's own diagram
267
356
  em watch <name>.em -o <name>.svg # re-render on save (run in background)
268
357
  em watch <name>.em -o <name>.svg --serve # + live viewer with instant push-reload (--port N)
358
+ # click "Review mode" for a slice-by-slice storyboard walkthrough
269
359
  ```
270
360
 
271
361
  Always finish a working session by: re-rendering, running `em validate`, and updating
272
- `.event-modeling.md` with the current phase/step, decisions, and open questions.
362
+ `.event-modeling.md` with the current phase/step, decisions, open questions, and a Usage log
363
+ entry (phases touched + validate diagnostic categories hit).
@@ -102,7 +102,16 @@ For each slice in scope:
102
102
  materialized projection in code — a direct event handler with criteria-sourced state is
103
103
  the same pattern expressed in the code idiom (extract.md's R5 reaction rules). That is
104
104
  NOT structural drift; note the idiom in the evidence log instead of reporting the view
105
- as missing.
105
+ as missing. This is the *general*, idiom-level version of the next bullet — prefer citing
106
+ an actual `divergence` annotation when the element carries one; fall back to this idiom
107
+ note only when it doesn't.
108
+ - **Accepted divergences:** before treating any finding as drift, check whether the
109
+ relevant canonical element already carries a `divergence "..."` annotation (visible in
110
+ the `.em`, in `em export --json`'s `divergence` field, or — for a structural finding — as
111
+ the `acceptedDivergence` field already attached to that `em diff --json` entry, see step
112
+ 3). If it does, this is not a fresh finding to litigate: classify it as **Accepted
113
+ divergence** (step 4) and cite the annotation text as the evidence. This is the specific,
114
+ per-element, ratified counterpart to the idiom-level bullet above.
106
115
  3. Write what you found into the scratch model (`<model-name>-asis.em`, see below). **Seed the
107
116
  scratch model as a copy of the canonical `.em` and replace only the in-scope slices** with
108
117
  the as-is picture, leaving out-of-scope slices byte-identical. This is not an optimization:
@@ -112,9 +121,13 @@ For each slice in scope:
112
121
  this phase exists to avoid. It also keeps the scratch model compilable (see step 3). Within
113
122
  the slices you do rewrite, **reuse the canonical model's slice and element names wherever
114
123
  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 }`
124
+ new name only to code behavior the model doesn't cover at all. When you do need a new name,
125
+ check `em glossary` across sibling `.em` models first (same aid the `extract` phase uses) —
126
+ if the term already exists elsewhere under a different kind or spelling, that's a signal to
127
+ reuse the established name rather than mint another synonym for the same concept. Record the
128
+ mapping decision for every element you name this way (e.g. `CartItemAdded` code class ↔
129
+ `"Cart Item Added"` model event) so it's auditable, not a silent judgment call. **Match the
130
+ canonical model's field granularity:** declare `{ fields }`
118
131
  in the scratch model only where the canonical model declares them (if the canonical model
119
132
  puts fields on commands but not events, so does the scratch model — writing code-derived
120
133
  event fields there would flood the diff with `field-added` noise). Schema claims at finer
@@ -135,7 +148,9 @@ what every finding in the report will cite — file paths, not vibes.
135
148
  First `em validate <model-name>-asis.em`. `em diff` refuses when either side has validation
136
149
  *errors* (warnings are fine), so a scratch model that doesn't compile stops the run cold. The
137
150
  usual cause is a slice that depends on one you rewrote incompletely — a `view X again` whose
138
- first declaration you dropped, or an automation `from` a read model that no longer exists.
151
+ first declaration you dropped, an automation `from` a read model that no longer exists, or an
152
+ `arrow` you wrote joining kinds the patterns don't connect. (An unread event is only a *warning*,
153
+ so it never blocks the diff — and in an as-is scratch model it's expected.)
139
154
  Seeding from the canonical model (step 2) prevents this; if it still fails, **fix the scratch
140
155
  model and report the failure as a tooling problem in the run metadata — never as drift.** A
141
156
  model you couldn't compile is an uncertainty about your own scratch file, not a finding
@@ -145,7 +160,10 @@ Then run `em diff <model-name>.em <model-name>-asis.em --json`. `em`, not you, d
145
160
  structurally differs — don't re-derive by eye what the tool already computed. Parse the JSON
146
161
  (`docs/cli.md`'s diff section documents the envelope: `diffSchemaVersion`, `counts`, `changes`,
147
162
  `removals`, every optional `ChangeEntry` field explicit-`null` when unused) and use it as the
148
- structural-surface findings directly.
163
+ structural-surface findings directly. Check each entry's `acceptedDivergence` field first: a
164
+ non-null value means `em` itself already found a `divergence` annotation on the canonical
165
+ element behind this entry — that finding is Accepted divergence (step 4), not something to
166
+ re-litigate.
149
167
 
150
168
  ### 4. Classify
151
169
 
@@ -155,6 +173,11 @@ step 2 — sorts into exactly one class:
155
173
  - **Real drift** — code evidence contradicts the model or doc.
156
174
  - **Model gap** — code evidence shows behavior neither the model nor the doc covers.
157
175
  - **Internal inconsistency** — the doc and the `.em` disagree with each other (surface 3 only).
176
+ - **Accepted divergence** — the relevant canonical element carries a `divergence "..."`
177
+ annotation (cited directly from a structural entry's `acceptedDivergence` field, or from the
178
+ `.em`/`em export` for a spec/internal finding). This is what the annotation is *for*: a
179
+ reasoned, ratified deviation stops being re-reported as drift on every run. Cite the
180
+ annotation text, not fresh judgment — that judgment already happened when it was written.
158
181
  - **Extraction uncertainty** — no evidence either way. Never reported as drift or a gap.
159
182
 
160
183
  Every classification must cite the evidence recorded in step 2 (or the `em diff` entry, for
@@ -167,7 +190,9 @@ Write `conformance/<YYYY-MM-DD>-report.md` in the model directory, from
167
190
  ready-to-apply red note — `issue "conformance: <text>"` on the right element, written out in
168
191
  the report exactly as it should be pasted into the `.em` — when a model-side marker is the
169
192
  right fix; when the fix is purely doc wording (or the finding is an internal inconsistency
170
- with no clear side to flag), omit the red note and say why instead. **You never edit the
193
+ with no clear side to flag), omit the red note and say why instead. **Never propose a red note
194
+ for a finding already classified Accepted divergence** — report it (so the annotation itself
195
+ stays auditable) but don't ask the user to ratify what's already ratified. **You never edit the
171
196
  canonical model or a slice doc unprompted** — walk the report with the user, apply only the
172
197
  proposals they approve, then re-render and `em validate`.
173
198
 
@@ -226,3 +251,7 @@ whatever cadence the user wants (see `docs/ci.md` for a scheduled-run recipe).
226
251
  - **Re-deriving what `em diff` already decided** — once you have the JSON, use it; don't
227
252
  eyeball the two `.em` files yourself looking for structural differences the tool already
228
253
  computed for you.
254
+ - **Re-litigating an accepted divergence** — a non-null `acceptedDivergence` on a diff entry
255
+ (or a `divergence` annotation on the element behind a spec/internal finding) means this exact
256
+ question was already reasoned through and ratified. Classify it as Accepted divergence and
257
+ cite the annotation; don't propose a new red note or re-open the judgment call from scratch.