@milehimikey/em 1.13.0 → 1.14.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 (239) hide show
  1. package/.claude/agents/em-critic.md +26 -0
  2. package/.claude/agents/em-implementer.md +34 -0
  3. package/.claude/agents/em-reviewer.md +31 -0
  4. package/.claude/agents/em-validator.md +27 -0
  5. package/.claude/skills/event-modeling/SKILL.md +52 -12
  6. package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
  7. package/.claude/skills/event-modeling-conform/reference/conform.md +1 -1
  8. package/.claude/skills/event-modeling-design/SKILL.md +140 -18
  9. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  10. package/.claude/skills/event-modeling-engagement/SKILL.md +192 -0
  11. package/.claude/skills/event-modeling-engagement/reference/engagement.md +143 -0
  12. package/.claude/skills/event-modeling-implement/SKILL.md +7 -4
  13. package/.claude/skills/event-modeling-implement/reference/implement.md +51 -12
  14. package/.claude/skills/event-modeling-review/SKILL.md +1 -1
  15. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +274 -188
  16. package/.claude/skills/event-modeling-shared/reference/operating-principles.md +61 -0
  17. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +116 -4
  18. package/.claude/skills/event-modeling-shared/templates/constitution.md +18 -1
  19. package/.claude/skills/event-modeling-shared/templates/slice.md +64 -39
  20. package/.claude/skills/event-modeling-shared/templates/state.md +5 -1
  21. package/.claude-plugin/marketplace.json +14 -0
  22. package/README.md +25 -11
  23. package/dist/catalog/apiFirst.d.ts +28 -0
  24. package/dist/catalog/apiFirst.js +49 -0
  25. package/dist/catalog/apiFirst.js.map +1 -0
  26. package/dist/catalog/docJoin.d.ts +13 -0
  27. package/dist/catalog/docJoin.js +11 -0
  28. package/dist/catalog/docJoin.js.map +1 -1
  29. package/dist/catalog/docModelConsistencyValidate.js +44 -4
  30. package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
  31. package/dist/catalog/invariantsValidate.d.ts +4 -0
  32. package/dist/catalog/invariantsValidate.js +102 -0
  33. package/dist/catalog/invariantsValidate.js.map +1 -0
  34. package/dist/catalog/scenarios.d.ts +15 -0
  35. package/dist/catalog/scenarios.js +97 -0
  36. package/dist/catalog/scenarios.js.map +1 -0
  37. package/dist/catalog/sliceDoc.d.ts +8 -0
  38. package/dist/catalog/sliceDoc.js +9 -0
  39. package/dist/catalog/sliceDoc.js.map +1 -1
  40. package/dist/catalog/sliceReadyValidate.d.ts +31 -4
  41. package/dist/catalog/sliceReadyValidate.js +66 -8
  42. package/dist/catalog/sliceReadyValidate.js.map +1 -1
  43. package/dist/catalog/sliceSections.d.ts +55 -0
  44. package/dist/catalog/sliceSections.js +317 -0
  45. package/dist/catalog/sliceSections.js.map +1 -0
  46. package/dist/cli/agentsMd.js +7 -1
  47. package/dist/cli/agentsMd.js.map +1 -1
  48. package/dist/cli/api.d.ts +90 -0
  49. package/dist/cli/api.js +318 -0
  50. package/dist/cli/api.js.map +1 -0
  51. package/dist/cli/ciInit.d.ts +88 -3
  52. package/dist/cli/ciInit.js +507 -50
  53. package/dist/cli/ciInit.js.map +1 -1
  54. package/dist/cli/conformScope.d.ts +28 -0
  55. package/dist/cli/conformScope.js +67 -0
  56. package/dist/cli/conformScope.js.map +1 -1
  57. package/dist/cli/coverage.d.ts +25 -2
  58. package/dist/cli/coverage.js +86 -12
  59. package/dist/cli/coverage.js.map +1 -1
  60. package/dist/cli/engagement.d.ts +210 -0
  61. package/dist/cli/engagement.js +444 -0
  62. package/dist/cli/engagement.js.map +1 -0
  63. package/dist/cli/engagementFile.d.ts +86 -0
  64. package/dist/cli/engagementFile.js +255 -0
  65. package/dist/cli/engagementFile.js.map +1 -0
  66. package/dist/cli/metrics.d.ts +10 -0
  67. package/dist/cli/metrics.js +49 -1
  68. package/dist/cli/metrics.js.map +1 -1
  69. package/dist/cli/pluginPin.d.ts +55 -0
  70. package/dist/cli/pluginPin.js +188 -0
  71. package/dist/cli/pluginPin.js.map +1 -0
  72. package/dist/cli/ratify.d.ts +27 -2
  73. package/dist/cli/ratify.js +112 -13
  74. package/dist/cli/ratify.js.map +1 -1
  75. package/dist/cli/reratify.d.ts +11 -4
  76. package/dist/cli/reratify.js +89 -35
  77. package/dist/cli/reratify.js.map +1 -1
  78. package/dist/cli/scopeInputs.d.ts +48 -0
  79. package/dist/cli/scopeInputs.js +176 -0
  80. package/dist/cli/scopeInputs.js.map +1 -0
  81. package/dist/cli/skillCheck.d.ts +15 -1
  82. package/dist/cli/skillCheck.js +22 -1
  83. package/dist/cli/skillCheck.js.map +1 -1
  84. package/dist/cli/skillDirs.d.ts +5 -0
  85. package/dist/cli/skillDirs.js +13 -1
  86. package/dist/cli/skillDirs.js.map +1 -1
  87. package/dist/cli/skillSync.d.ts +4 -0
  88. package/dist/cli/skillSync.js +23 -0
  89. package/dist/cli/skillSync.js.map +1 -1
  90. package/dist/cli/sliceNew.d.ts +11 -7
  91. package/dist/cli/sliceNew.js +91 -18
  92. package/dist/cli/sliceNew.js.map +1 -1
  93. package/dist/cli/sliceSync.d.ts +50 -0
  94. package/dist/cli/sliceSync.js +170 -0
  95. package/dist/cli/sliceSync.js.map +1 -0
  96. package/dist/cli/status.d.ts +24 -1
  97. package/dist/cli/status.js +20 -1
  98. package/dist/cli/status.js.map +1 -1
  99. package/dist/cli/systemInputs.d.ts +53 -10
  100. package/dist/cli/systemInputs.js +186 -11
  101. package/dist/cli/systemInputs.js.map +1 -1
  102. package/dist/cli/upgrade.d.ts +30 -7
  103. package/dist/cli/upgrade.js +434 -50
  104. package/dist/cli/upgrade.js.map +1 -1
  105. package/dist/cli/usageLog.d.ts +4 -2
  106. package/dist/cli/usageLog.js +4 -2
  107. package/dist/cli/usageLog.js.map +1 -1
  108. package/dist/cli.js +631 -100
  109. package/dist/cli.js.map +1 -1
  110. package/dist/emit/api.d.ts +29 -0
  111. package/dist/emit/api.js +220 -0
  112. package/dist/emit/api.js.map +1 -0
  113. package/dist/emit/apiCheckJson.d.ts +3 -0
  114. package/dist/emit/apiCheckJson.js +21 -0
  115. package/dist/emit/apiCheckJson.js.map +1 -0
  116. package/dist/emit/codeownersJson.d.ts +3 -0
  117. package/dist/emit/codeownersJson.js +19 -0
  118. package/dist/emit/codeownersJson.js.map +1 -0
  119. package/dist/emit/coverageJson.d.ts +5 -2
  120. package/dist/emit/coverageJson.js +6 -2
  121. package/dist/emit/coverageJson.js.map +1 -1
  122. package/dist/emit/diffJson.d.ts +1 -1
  123. package/dist/emit/diffJson.js +8 -1
  124. package/dist/emit/diffJson.js.map +1 -1
  125. package/dist/emit/engagementJson.d.ts +8 -0
  126. package/dist/emit/engagementJson.js +46 -0
  127. package/dist/emit/engagementJson.js.map +1 -0
  128. package/dist/emit/glossaryJson.d.ts +1 -1
  129. package/dist/emit/glossaryJson.js +4 -1
  130. package/dist/emit/glossaryJson.js.map +1 -1
  131. package/dist/emit/json.d.ts +17 -1
  132. package/dist/emit/json.js +30 -1
  133. package/dist/emit/json.js.map +1 -1
  134. package/dist/emit/metricsJson.d.ts +1 -1
  135. package/dist/emit/metricsJson.js +4 -1
  136. package/dist/emit/metricsJson.js.map +1 -1
  137. package/dist/emit/queryJson.d.ts +1 -1
  138. package/dist/emit/queryJson.js +7 -1
  139. package/dist/emit/queryJson.js.map +1 -1
  140. package/dist/emit/scopeJson.d.ts +12 -0
  141. package/dist/emit/scopeJson.js +26 -0
  142. package/dist/emit/scopeJson.js.map +1 -0
  143. package/dist/emit/skillCheckJson.d.ts +3 -2
  144. package/dist/emit/skillCheckJson.js +10 -2
  145. package/dist/emit/skillCheckJson.js.map +1 -1
  146. package/dist/emit/statusJson.d.ts +1 -1
  147. package/dist/emit/statusJson.js +15 -1
  148. package/dist/emit/statusJson.js.map +1 -1
  149. package/dist/emit/systemJson.d.ts +15 -8
  150. package/dist/emit/systemJson.js +28 -13
  151. package/dist/emit/systemJson.js.map +1 -1
  152. package/dist/emit/upgradeJson.d.ts +1 -1
  153. package/dist/emit/upgradeJson.js +4 -1
  154. package/dist/emit/upgradeJson.js.map +1 -1
  155. package/dist/emit/validateJson.d.ts +3 -3
  156. package/dist/emit/validateJson.js +10 -4
  157. package/dist/emit/validateJson.js.map +1 -1
  158. package/dist/mcp/server.d.ts +1 -1
  159. package/dist/mcp/server.js +186 -37
  160. package/dist/mcp/server.js.map +1 -1
  161. package/dist/model/diff.d.ts +4 -1
  162. package/dist/model/diff.js +44 -8
  163. package/dist/model/diff.js.map +1 -1
  164. package/dist/model/glossary.d.ts +2 -0
  165. package/dist/model/glossary.js +1 -0
  166. package/dist/model/glossary.js.map +1 -1
  167. package/dist/model/model.d.ts +55 -2
  168. package/dist/model/model.js +57 -0
  169. package/dist/model/model.js.map +1 -1
  170. package/dist/model/qualifiedRef.d.ts +18 -0
  171. package/dist/model/qualifiedRef.js +26 -0
  172. package/dist/model/qualifiedRef.js.map +1 -1
  173. package/dist/model/queryIndex.d.ts +9 -1
  174. package/dist/model/queryIndex.js +18 -2
  175. package/dist/model/queryIndex.js.map +1 -1
  176. package/dist/model/rules.d.ts +143 -2
  177. package/dist/model/rules.js +162 -3
  178. package/dist/model/rules.js.map +1 -1
  179. package/dist/model/validate.d.ts +40 -0
  180. package/dist/model/validate.js +147 -1
  181. package/dist/model/validate.js.map +1 -1
  182. package/dist/parser/ast.d.ts +31 -3
  183. package/dist/parser/parser.js +148 -14
  184. package/dist/parser/parser.js.map +1 -1
  185. package/dist/query/format.js +1 -1
  186. package/dist/query/format.js.map +1 -1
  187. package/dist/query/verbs.d.ts +6 -0
  188. package/dist/query/verbs.js +6 -2
  189. package/dist/query/verbs.js.map +1 -1
  190. package/dist/system/adaptation.d.ts +14 -0
  191. package/dist/system/adaptation.js +49 -0
  192. package/dist/system/adaptation.js.map +1 -0
  193. package/dist/system/codeowners.d.ts +77 -0
  194. package/dist/system/codeowners.js +242 -0
  195. package/dist/system/codeowners.js.map +1 -0
  196. package/dist/system/manifest.d.ts +10 -5
  197. package/dist/system/manifest.js +54 -26
  198. package/dist/system/manifest.js.map +1 -1
  199. package/dist/system/migrateManifest.d.ts +28 -0
  200. package/dist/system/migrateManifest.js +155 -0
  201. package/dist/system/migrateManifest.js.map +1 -0
  202. package/dist/system/scope.d.ts +46 -0
  203. package/dist/system/scope.js +132 -0
  204. package/dist/system/scope.js.map +1 -0
  205. package/dist/system/verify.d.ts +50 -13
  206. package/dist/system/verify.js +179 -58
  207. package/dist/system/verify.js.map +1 -1
  208. package/dist/templates.d.ts +2 -2
  209. package/dist/templates.js +23 -2
  210. package/dist/templates.js.map +1 -1
  211. package/package.json +6 -3
  212. package/plugin/.claude-plugin/plugin.json +18 -0
  213. package/plugin/agents/em-critic.md +27 -0
  214. package/plugin/agents/em-implementer.md +35 -0
  215. package/plugin/agents/em-reviewer.md +32 -0
  216. package/plugin/agents/em-validator.md +28 -0
  217. package/plugin/shared/reference/em-dsl.md +866 -0
  218. package/plugin/shared/reference/methodology.md +329 -0
  219. package/plugin/shared/reference/operating-principles.md +222 -0
  220. package/plugin/shared/reference/slice-doc-schema.md +566 -0
  221. package/plugin/shared/templates/conformance-report.md +85 -0
  222. package/plugin/shared/templates/constitution.md +136 -0
  223. package/plugin/shared/templates/model-readme.md +46 -0
  224. package/plugin/shared/templates/slice.md +251 -0
  225. package/plugin/shared/templates/state.md +77 -0
  226. package/plugin/skills/conform/SKILL.md +93 -0
  227. package/plugin/skills/conform/reference/conform.md +428 -0
  228. package/plugin/skills/design/SKILL.md +297 -0
  229. package/plugin/skills/discover/SKILL.md +90 -0
  230. package/plugin/skills/discover/reference/extract.md +147 -0
  231. package/plugin/skills/engagement/SKILL.md +193 -0
  232. package/plugin/skills/engagement/reference/engagement.md +144 -0
  233. package/plugin/skills/event-modeling/SKILL.md +132 -0
  234. package/plugin/skills/implement/SKILL.md +61 -0
  235. package/plugin/skills/implement/reference/implement.md +439 -0
  236. package/plugin/skills/review/SKILL.md +74 -0
  237. package/dist/emit/typespec.d.ts +0 -26
  238. package/dist/emit/typespec.js +0 -356
  239. package/dist/emit/typespec.js.map +0 -1
@@ -0,0 +1,566 @@
1
+ <!-- GENERATED FILE from .claude/skills/event-modeling-shared/reference/slice-doc-schema.md by scripts/build-plugin.ts — do not hand-edit; run npm run docs:generate -->
2
+ <!-- GENERATED FILE — do not hand-edit. Synced from docs/slice-doc-schema.md by
3
+ scripts/generate-skill-docs.ts (`npm run docs:generate`); `npm run docs:check` fails
4
+ on drift. A few links below are rewritten for this vendored location (see that
5
+ script's buildVendoredSliceDocSchema for exactly what changes and why) — everything
6
+ else is identical to the source doc. -->
7
+
8
+ # Slice-doc frontmatter schema
9
+
10
+ This documents the machine-read YAML frontmatter dialect `src/catalog/sliceDoc.ts` parses out
11
+ of `slices/<slice-key>.md` — the contract `em export`'s slice-frontmatter join reads. It covers
12
+ the **frontmatter block only**. Body/prose authoring conventions (Intent, Scenarios, Open
13
+ Questions, …) live in the event-modeling skill's
14
+ [`templates/slice.md`](../templates/slice.md), not here — with two
15
+ exceptions:
16
+
17
+ - The `## Open Questions` section's GFM checkboxes (`- [ ]` / `- [x]`) *are* now machine-parsed
18
+ by `sliceDoc.ts` too (`openQuestionsTotal`/`openQuestionsUnchecked`, MIL-87), feeding
19
+ `em validate --slice-ready <key>` (see [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)).
20
+ The section's cross-version lifecycle (MIL-156) is documented here too, alongside `## Delta`'s
21
+ — see [Open Questions section: lifecycle](#open-questions-section-lifecycle) below. Everything
22
+ else about the section's authoring — what a good open question looks like, when to resolve one
23
+ — stays owned by the skill template; this doc only covers the counting mechanics and lifecycle.
24
+ - The `## Delta` section's grammar and lifecycle (MIL-88) is documented here, in full, alongside
25
+ the lineage keys it complements — a 2026-08-14 ruling deliberately kept the slice-doc contract
26
+ as one document rather than splitting a structurally-related convention across two files, even
27
+ though (unlike Open Questions) `## Delta` is currently display-only prose that no `em` command
28
+ parses yet. See [Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle)
29
+ below; authoring voice/tone for the section still lives in the skill template, same as every
30
+ other body section.
31
+
32
+ ## How the parser reads frontmatter
33
+
34
+ A leading `---` / `---` fence, at the literal start of the file (like Jekyll/Hugo — a guidance
35
+ comment above the fence means the frontmatter doesn't count yet). Inside the fence: top-level
36
+ scalar `key: value` lines only — **no lists, no nesting, no real YAML**. Quoted values have
37
+ their outer quotes stripped; a value that's empty after trimming is dropped. Keys are matched
38
+ case-sensitively as `[A-Za-z][\w-]*` but folded to lowercase for lookup. Any line that doesn't
39
+ match that shape — including `#`-prefixed comment lines, blank lines, and YAML list
40
+ continuation lines (` - item`) — is silently skipped. An unterminated fence (no closing `---`)
41
+ means "no frontmatter at all": the whole file is treated as body.
42
+
43
+ No `yaml` dependency, by design — a real YAML parser was added and reverted (commit `6de0a05`,
44
+ "keep the Two Laws of the Timeline"). This is a deliberately shallow line-scan, and stays that
45
+ way: real YAML would also start accepting nesting/lists that this schema explicitly doesn't
46
+ define a meaning for.
47
+
48
+ `SliceDoc` (`src/catalog/sliceDoc.ts`) also exposes `frontmatterPresent` (was a well-formed
49
+ fence found and closed at all) and `missingRequiredFields` (which of the required-at-every-
50
+ status keys, above, were absent) — the mechanical basis `em export`'s doc join (MIL-91) uses
51
+ to decide `frontmatter-invalid` without re-deriving frontmatter-shape rules of its own.
52
+
53
+ ## Canonical keys
54
+
55
+ | Key | Type | Grammar / enum | Read by |
56
+ |---|---|---|---|
57
+ | `schemaVersion` | integer | `1` (current dialect version) | its *value* is not read back by any `em` command today (reserved) — but its *presence* is required by `hasUsableFrontmatter()`: a doc omitting it is `frontmatter-invalid` (see below) |
58
+ | `pattern` | string | `state-change` \| `state-view` \| `automation` \| `translation` | authored/informational only — `em catalog` and `em export` both derive pattern from the `.em` AST instead (`em export`'s `slice.pattern`, schema `1.4`) |
59
+ | `swimlane` | string | free text, `<Persona> → <Context>` | display-only |
60
+ | `status` | string, case-insensitive | `draft` \| `reviewed` \| `ready-to-implement` \| `implemented` | written by `em slice review` (→ `reviewed`, MIL-201), `em slice ratify`/`em slice reratify` (→ `ready-to-implement`) and `em slice mark-implemented` (→ `implemented`); read by `em catalog`, `em render`/`em watch` header coloring; joined into `em export`'s `slice.doc.status` (schema `1.4`); paired with `implementedIn` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
61
+ | `version` | integer | positive integer, starts at `1` | joined into `em export`'s `slice.doc.version` (schema `1.4`, MIL-91) |
62
+ | `implementedIn` | string | free text (PR/commit link) | joined into `em export`'s `slice.doc.implementedIn` (schema `1.4`, MIL-91); paired with `status` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
63
+ | `split-from` | single ref | `<slice-key>@v<N>` | joined into `em export`'s `slice.doc.splitFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
64
+ | `merged-from` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.mergedFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
65
+ | `superseded-by` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.supersededBy` (schema `1.4`, MIL-91) and `em diff`'s `slice-removed` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
66
+ | `covers` | list of slice keys | comma-separated `<slice-key>, ...` (plain keys — **not** the `<slice-key>@v<N>` ref grammar above) | ratifies a cross-slice binding (MIL-121 — see [Cross-slice coverage](#cross-slice-coverage-covers) below): read by `em export`'s doc join, `em validate --slice-ready`, and (best-effort) `em render`/`em watch`'s Slice Status legend |
67
+ | `reviewedBy` | string | free text (typically the facilitator's or reviewer's name) | written only by `em slice review` (MIL-201 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-review-file-slice-key---by-name)); joined into `em export`'s `slice.doc.reviewedBy` (schema `1.11`) and `em slice index`'s Reviewed by column |
68
+ | `reviewedOn` | string | `YYYY-MM-DD` | written only by `em slice review` (MIL-201); joined into `em export`'s `slice.doc.reviewedOn` (schema `1.11`) |
69
+ | `ratifiedBy` | string | free text (typically a person's name) | written only by `em slice ratify` (MIL-165 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name)); joined into `em export`'s `slice.doc.ratifiedBy` (schema `1.8`) and `em slice index`'s Ratified by column |
70
+ | `ratifiedOn` | string | `YYYY-MM-DD` | written only by `em slice ratify` (MIL-165); joined into `em export`'s `slice.doc.ratifiedOn` (schema `1.8`) |
71
+ | `meaningConfirmed` | boolean | `true` | written only by `em slice ratify`/`reratify --meaning-unchanged` (MIL-238 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name)): the API-first sign-off on a public-touching slice — this version does not change what the public contract means. Joined into `em export`'s `slice.doc.meaningConfirmed` (schema `1.15`; `false` when absent) |
72
+ | `contractChange` | string | quoted free text (`contractChange: "<why>"`) | written only by `em slice ratify`/`reratify --contract-change "<why>"` (MIL-238): the API-first sign-off on a public-touching slice — why a consumer must read this change differently. Joined into `em export`'s `slice.doc.contractChange` (schema `1.15`, quotes stripped; `null` when absent) |
73
+ | `owner` | string | free text (typically a person or team name) | hand-filled — no `em` command writes it; joined into `em export`'s `slice.doc.owner` (schema `1.9`, MIL-171), `em slice index`'s Owner column, and `em status`'s per-slice `owners[]` |
74
+ | `tracking` | string | free text (typically an external ticket/issue URL) | hand-filled — no `em` command writes it; joined into `em export`'s `slice.doc.tracking` (schema `1.9`, MIL-171) and `em slice index`'s Tracking column. This is the exact field `em-tracker-bridge` reads to find the ticket mirroring this slice — `em` only stores and displays it, it never talks to a tracker itself |
75
+ | `conformedVersion` | integer | positive integer | written only by `em slice conform` (MIL-214 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-conform-file-slice-key---at-rev)): this slice's `version:` at the time a conform sweep last certified it. Joined into `em export`'s `slice.doc.conformedVersion` (schema `1.13`); paired with `version` to compute `slice.doc.driftSignal`'s `"uncertified"` case |
76
+ | `conformedAt` | string | free text (a target-repo revision, NOT a `<slice-key>@v<N>` lineage ref) | written only by `em slice conform` (MIL-214): the revision that certification sweep diffed against. Joined into `em export`'s `slice.doc.conformedAt` (schema `1.13`) and `em slice index`'s Conformed column |
77
+ | `conformedOn` | string | `YYYY-MM-DD` | written only by `em slice conform` (MIL-214). Joined into `em export`'s `slice.doc.conformedOn` (schema `1.13`) |
78
+
79
+ Five keys — `schemaVersion`/`pattern`/`swimlane`/`status`/`version` — are what
80
+ `hasUsableFrontmatter()` requires: a doc omitting any of them is `frontmatter-invalid` to
81
+ `em export`'s doc join and to `em validate --slice-ready` (the parser itself never *throws*
82
+ over an omission — it reports). `implementedIn` and the lineage keys are the genuinely
83
+ optional ones; `implementedIn`'s required-once-implemented rule in the table below is
84
+ convention, not parser-enforced.
85
+
86
+ ## Required vs optional, by `status`
87
+
88
+ | Key | draft | reviewed | ready-to-implement | implemented |
89
+ |---|---|---|---|---|
90
+ | `schemaVersion`, `pattern`, `swimlane`, `status` | required | required | required | required |
91
+ | `version` | required (starts at `1`) | required | required | required |
92
+ | `implementedIn` | optional | optional | optional | required once the slice has *ever* reached `implemented` — may still name a **prior** version's PR during re-ratification, see below |
93
+ | `split-from`, `merged-from`, `superseded-by` | optional in every state — present only on docs created by a split/merge, or on a doc that has been retired |
94
+ | `covers` | optional in every state — present only on a doc that deliberately also serves another slice (MIL-121, see below); most docs never carry it |
95
+ | `reviewedBy`, `reviewedOn` | optional in every state — present once `em slice review` (MIL-201) has run at least once; a doc that never went through the review gate, or was hand-flipped to `reviewed`, simply omits both. `em slice ratify` never clears them; `em slice reratify` does (they describe the version that shipped) |
96
+ | `ratifiedBy`, `ratifiedOn` | optional in every state, but `ready-to-implement` needs `ratifiedBy` to pass `em validate --slice-ready` (`slice-ready-not-ratified`, MIL-259) — present once `em slice ratify` (MIL-165) has run; `em slice reratify` clears both. `em upgrade` (`ratified-signoff`, 1.14) grandfathers a `version: 1` `ready-to-implement` doc that never had one by writing `ratifiedBy: "grandfathered (unsigned before em 1.14)"` and `ratifiedOn:` the upgrade date |
97
+ | `meaningConfirmed`, `contractChange` | optional in every state, at most one present — written right after `ratifiedOn:` by `em slice ratify` and right after `version:` by `em slice reratify` (MIL-238). On a **public-touching** slice (one owning a `public` command, event or view) both commands refuse without `--meaning-unchanged` or `--contract-change "<why>"`, so a doc ratified on 1.14+ carries one; elsewhere either is optional. `em slice reratify` clears both before writing its own (they describe the prior version, same as `reviewedBy`/`ratifiedBy`). `em status` counts public-touching `ready-to-implement`/`implemented` docs with neither (`publicSlicesUnconfirmed`) |
98
+ | `owner`, `tracking` | optional in every state — hand-filled whenever a team wants a who-holds-this / external-tracker link; most docs never carry either (MIL-171) |
99
+ | `conformedVersion`, `conformedAt`, `conformedOn` | optional in every state — present once `em slice conform` (MIL-214) has run at least once for the CURRENT version; a doc that's never been through a conform sweep, or whose `version` has since bumped past what was certified, simply has none of the three (or a stale triple — see `driftSignal`'s `uncertified` case below) |
100
+
101
+ `em export`'s slice-doc join (MIL-91) is the first `em` command to mechanically check the
102
+ required row above: a bound doc missing any of `schemaVersion`/`pattern`/`swimlane`/`status`/
103
+ `version` (or missing a frontmatter block entirely) reports `slice.doc.reason:
104
+ "frontmatter-invalid"` plus a warning diagnostic — report, never fail, same as every other
105
+ export finding. `implementedIn`'s conditional requirement (once a slice has *ever* reached
106
+ `implemented`) is **not** checked — that needs git history no doc-only parse has access to.
107
+ No `em` command fails a build over this table. `em validate`'s frontmatter-coherence check
108
+ (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) warns
109
+ (never fails) on the one combination checkable without git history: `status: implemented` with
110
+ no `implementedIn` link at all.
111
+
112
+ ## Stub docs
113
+
114
+ `em slice new --stub` and `em slice stub-all` (MIL-184, ruled 2026-09-08 on GH #128 — see that
115
+ issue for the fuller design rationale) write a **stub**: the exact same 5 required-at-`status:
116
+ draft` frontmatter keys above, but a one-line placeholder body (`_Stub — deepen with the slice
117
+ phase (see slice-doc-schema.md)._`) instead of the diagram-image stub and every judgment section
118
+ (Intent, Command, Scenarios, Open Questions, …) a full slice doc eventually carries.
119
+
120
+ A stub is not a lesser doc, frontmatter-wise — it's a real doc with a real `status`, parsed and
121
+ joined by `em export`/`em catalog`/`em status`/`em slice index` exactly like a fully-written one,
122
+ because it's the same frontmatter dialect. The doc stays the single source of truth for
123
+ `status`; a stub is just the cheapest possible way to give a slice one before anyone's ready to
124
+ write the real spec.
125
+
126
+ **A stub CAN pass `em validate --slice-ready`.** That gate checks six things (`computeSliceReadyGates`,
127
+ [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)): a doc is bound, its frontmatter
128
+ is usable, `status` is `ready-to-implement`, it carries a `ratifiedBy` (MIL-259), its Open Questions count has none unchecked, and — for a slice owning a `public` element — the model's contract is current (MIL-238; nothing in the doc). A
129
+ stub with no `## Open Questions` section at all has `openQuestionsTotal: 0` (see
130
+ [Open Questions section: lifecycle](#open-questions-section-lifecycle) below) — zero unchecked is
131
+ vacuously true — so a stub escalated to `ready-to-implement` (`em slice stub-all --status
132
+ ready-to-implement --by <name>`, which records the ratifier) passes cleanly. The readiness gate is frontmatter-plus-checkbox-
133
+ count only; it has no opinion about whether Intent/Command/Scenarios/Invariants were ever
134
+ written. Demanding that content is the `slice` phase's (and, for a live walkthrough, the review
135
+ skill's) job, not a machine gate's — see `../../skills/design/SKILL.md` and
136
+ `../../skills/review/SKILL.md`.
137
+
138
+ ## Lineage: grammar and cardinality
139
+
140
+ Grammar: `<slice-key>@v<N>` — `<slice-key>` is the referenced slice's kebab-case filename stem
141
+ (matches `kebabSlug()`, `src/util/slug.ts`); `<N>` is a positive integer, the referenced
142
+ slice's `version` at the point of reference.
143
+
144
+ | Key | Cardinality | Why |
145
+ |---|---|---|
146
+ | `split-from` | **single** ref | A slice splits *from* exactly one parent, even though a split can produce multiple successors. |
147
+ | `merged-from` | **list**, comma-separated | A merge combines two or more source slices into one surviving doc. |
148
+ | `superseded-by` | **list**, comma-separated | The retired side of a split names potentially multiple successors; the retired side of a merge or a simple rename usually names exactly one, but the grammar stays list-shaped either way. |
149
+
150
+ **Worked example** (a split): `checkout` v4 splits into `checkout` v5 plus a new
151
+ `apply-discount` v1. The retired parent doc, `checkout.md`, gets:
152
+
153
+ ```yaml
154
+ version: 4
155
+ status: implemented
156
+ superseded-by: checkout@v5, apply-discount@v1
157
+ ```
158
+
159
+ Both surviving docs carry the back-link — `checkout.md` (now at v5) and `apply-discount.md`
160
+ each get:
161
+
162
+ ```yaml
163
+ split-from: checkout@v4
164
+ ```
165
+
166
+ **Scope note:** the parser (`sliceDoc.ts`) itself still never checks that a referenced
167
+ slice/version actually *exists* — that stays deliberately out of scope here, by design (fs-free,
168
+ pure). A malformed or dangling ref still parses (its `raw` text is preserved, `sliceKey`/
169
+ `version` come back `null` for a malformed value); referential checking is a separate, fs-aware
170
+ layer.
171
+
172
+ `em validate` (`src/catalog/lineageValidate.ts`, MIL-84) is that layer. It checks what the
173
+ *current tree* can prove wrong and stays silent about what it can't — see
174
+ [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage) for exactly what's checked (grammar,
175
+ self-reference/cycles, dangling `superseded-by`, impossible version arithmetic) versus what's
176
+ deliberately never flagged (a `split-from`/`merged-from` naming a key legitimately absent from
177
+ the current tree — the normal state after a real split/merge). Deep historical verification —
178
+ did `slice@vN` really, once, exist? — stays out of core validate; the reason that's sufficient
179
+ is a same-commit authoring convention, not new plumbing (`em ledger`, below, is the opt-in,
180
+ two-revision command for the related-but-different version/content invariant):
181
+
182
+ > Lineage refs (`split-from:`/`merged-from:`/`superseded-by:`) are written in the same
183
+ > ratification commit that performs the operation they record, so the PR diff contains both
184
+ > sides of the claim (the predecessor at its final version, and the ref naming it). The review
185
+ > airlock is the history check.
186
+
187
+ ## Cross-slice coverage: `covers`
188
+
189
+ Since `em` 1.7.1 (MIL-120), the canonical Automation/Translation shape is two raw slices: a
190
+ bare `view` slice, and the reaction+command+event slice that reads it. Each slice can still bind
191
+ its own `slices/<key>.md` doc the ordinary way, but a team documenting the whole design unit as
192
+ **one** doc needs a way to make the view-only slice count as doc-bound too, without splitting
193
+ the write-up back into two files. `covers` (MIL-121) is that escape hatch — a deliberately
194
+ two-ended handshake, not a one-sided claim:
195
+
196
+ 1. In the `.em`, an element in the *covered* slice (the one with no doc of its own) carries
197
+ `note "slices/<other-key>.md"` — a path in the ordinary `slices/<key>.md` shape, just naming
198
+ a *different* slice's canonical doc instead of its own.
199
+ 2. That other doc's own frontmatter ratifies the coverage with `covers`, naming the covered
200
+ slice's key back — same comma-separated, single scalar-line shape as `merged-from`/
201
+ `superseded-by` (this parser's frontmatter dialect has **no** real YAML list support, see
202
+ above):
203
+ ```yaml
204
+ covers: detect-unpaid-orders
205
+ ```
206
+ Multiple covered slices: `covers: detect-unpaid-orders, some-other-slice`.
207
+
208
+ Both sides are required. A `note` naming another slice's doc with no matching `covers` entry on
209
+ that doc — the file is missing, its frontmatter isn't usable, or `covers` doesn't list this
210
+ slice back — leaves the noting slice exactly as unbound as if it had no note at all
211
+ (`no-doc-bound`); em never guesses at a one-sided claim. When ratified, every consumer that
212
+ reads a bound doc's canonical fields (`em export`'s `slice.doc`, `em validate --slice-ready`,
213
+ `em render`/`em watch`'s Slice Status legend) reads the **covering** doc's `status`/`version`/
214
+ `implementedIn`/lineage/Open Questions, not the covered slice's own (nonexistent) file. A doc
215
+ covers its own canonical slice key implicitly — `covers` never needs to list it.
216
+
217
+ `em catalog` (MIL-137) is one such consumer, via the same filename+frontmatter `covers:` scan
218
+ (`src/catalog/coverage.ts`), never the `note` clause itself: a covered slice's detail page shows
219
+ the covering doc's Status, but a "Documented as part of &lt;covering slice&gt;" banner instead
220
+ of the covering doc's body — linking to the covering slice's own catalog page rather than
221
+ inlining content authored under a different slice's name.
222
+
223
+ `covers` is a plain list of slice keys (`<slice-key>`, e.g. `detect-unpaid-orders`), **not** the
224
+ `<slice-key>@v<N>` ref grammar the lineage keys use above: coverage is a standing "this doc also
225
+ serves that slice" declaration, with no version component — unlike lineage, which records a
226
+ specific historical hop.
227
+
228
+ **Out of scope for THIS join:** `resolveSliceDocJoin` itself never warns on a *mismatched*
229
+ cross-note (a note naming a doc that doesn't ratify it back) — the slice's doc join still stays
230
+ silently `no-doc-bound`, the same as never having written the note; the join's own job is
231
+ resolving the *winning* binding, not auditing every note that didn't win. `em validate` does flag
232
+ the mismatch, as its own separate rule (MIL-126) — see
233
+ [validation.md#note-binding-mismatch](https://github.com/milehimikey/em/blob/main/docs/validation.md#note-binding-mismatch): a dangling cross-note
234
+ (missing file), one pointing at a doc with unusable frontmatter, one pointing at a usable doc
235
+ that just doesn't ratify (`covers` doesn't list this slice back), and an extra note doing nothing
236
+ in a slice that's already bound elsewhere, each get their own diagnostic there.
237
+
238
+ ## Continuations
239
+
240
+ Since `em` 1.12.0 (MIL-208): a `view X again` instance is a **continuation** of the slice
241
+ holding `X`'s first (originating) declaration — one read model, fed by more than one event over
242
+ the timeline — not a spec unit of its own. Unlike `covers` above, this is fully automatic and
243
+ structural: no frontmatter, no `note`, nothing to author. A slice is a continuation iff every
244
+ non-`ui` element it declares is a `view` with `again === true`; the doc join resolves it
245
+ straight through to the originating slice's own doc, exactly the way a ratified `covers:`
246
+ binding resolves a covered slice — same `doc.found`/`status`/`driftSignal`, plus a
247
+ `continuationOf: <originating-key>` field naming why. `em export`, `em status`, `em coverage`,
248
+ `em slice index`, `em catalog`, and the render pipeline's Slice Status legend all resolve a
249
+ continuation slice this way; `em slice ratify`/`review`/`reratify`/`mark-implemented` and
250
+ `em slice new --wire` refuse on one outright, naming the originating slice to act on instead —
251
+ a continuation slice has no status/ratification of its own to flip.
252
+
253
+ The originating slice's own `em export` object also gains `alsoReads: [{ event, atSlice }]` —
254
+ the union of events every later `again` instance of that view reads via `from`, in timeline
255
+ order — so implementing the read model from one export never means walking every `again`
256
+ slice's doc by hand to assemble the full event list.
257
+
258
+ **Authoring convention:** a continuation's behavior belongs in the originating doc's own
259
+ `## Scenarios (Given / When / Then)` section — one scenario per event a later `again` instance
260
+ feeds — rather than in a doc of its own; no new frontmatter is needed to record it.
261
+
262
+ **Legacy own doc wins.** A slice that would otherwise be a continuation but still carries its
263
+ own doc — a `note "slices/<again-key>.md"` binding it, or a stray `slices/<again-key>.md` file
264
+ left over from before this rule existed — keeps today's per-instance behavior unchanged: its own
265
+ doc, its own status, its own ratification. `em validate`'s `continuation-has-own-doc` warning
266
+ (see [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md)) flags it either way, so an author can fold that doc's
267
+ scenarios into `slices/<originating-key>.md` and delete it. This is the migration path — an
268
+ existing model with per-`again`-instance docs keeps working exactly as before until each one is
269
+ folded in.
270
+
271
+ ## Delta section: grammar and lifecycle
272
+
273
+ `## Delta` is the body section that pairs with a re-ratification (see "`status` under
274
+ re-ratification" below) — a structured record of what changed to produce the version currently
275
+ in the frontmatter, in typed operation blocks (MIL-88) rather than a one-line summary. Like the
276
+ lineage keys above, it's currently authored, not parsed — no `em` command reads *this section*
277
+ — but it's documented here rather than left purely to the skill template because its shape is a
278
+ structural contract other tooling may parse later, same reasoning as the lineage grammar.
279
+ `em diff --json` now emits the same four-word vocabulary on its own structural entries (`op:
280
+ "Added" | "Modified" | "Removed" | "Renamed"`, schema `1.7`, MIL-131 — see
281
+ [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-diff-old-new)), so the two sides speak the same language; comparing an
282
+ authored `## Delta` against the structural diff of the revisions it claims to describe is still
283
+ a separate, not-yet-built decision.
284
+
285
+ **The heading is always the literal string `## Delta` — never a variable heading** (e.g. never
286
+ `## Delta: v1 → v2`). Several consumers key on exact `##` text when locating sections in a slice
287
+ doc (the SDD-bridge's section splitter, the slice-to-spec mapping table, the redirect prompt
288
+ preambles); a heading that changes every ratification would break exact-match lookup and move
289
+ the anchor on every hop — the same problem a made-up per-doc key would cause anywhere else in
290
+ this schema. Version range, ratification date, and a one-line summary go on the **first line
291
+ inside** the section instead, as display text — not a frontmatter key, not parsed:
292
+
293
+ ```markdown
294
+ ## Delta
295
+ **v1 → v2, ratified 2026-08-12** — one-line summary of the ratified change.
296
+
297
+ ### Added
298
+ #### Requirement: {{title}} ({{stable ID, e.g. an INV-<MNEMONIC>-n from ## Invariants}})
299
+ {{requirement text}}
300
+ ##### Scenario: {{name}}
301
+ - **Given:** {{...}}
302
+ - **When:** {{...}}
303
+ - **Then:** {{...}}
304
+
305
+ ### Modified
306
+ (same shape as Added — the ID names the requirement that changed)
307
+
308
+ ### Removed
309
+ #### Requirement: {{title}} ({{stable ID}})
310
+ {{why it was removed}}
311
+
312
+ ### Renamed
313
+ - {{old title}} ({{old ID}}) → {{new title}} ({{new ID}})
314
+ ```
315
+
316
+ Stable IDs follow `INV-<MNEMONIC>-<n>` — a short (2-4 uppercase letters/digits) abbreviation of
317
+ the slice's key, unique among the model's slices, then a small integer, optionally letter-suffixed
318
+ for a closely-related sub-invariant (`INV-CHK-3a`) — see the Invariants section guidance in
319
+ [`templates/slice.md`](../templates/slice.md). Per-slice prefixes
320
+ make IDs globally unique across the model, so an `em coverage` citation is exact instead of
321
+ ambiguous across slices.
322
+
323
+ **Four `###` subsections, in that fixed order, Title Case singular words** — `Added`, `Modified`,
324
+ `Removed`, `Renamed` — not OpenSpec's shouting-case `ADDED Requirements` grouping. This is the
325
+ one deliberate divergence from the OpenSpec grammar the convention was validated against: the
326
+ four *operations* are retained (independent convergent design — OpenSpec arrived at the same
327
+ vocabulary from its own change-management-first angle), but OpenSpec's flat, file-per-delta
328
+ heading topology is not, because a slice doc's delta is a section inside a doc with its own
329
+ parsed heading contract, not a standalone file. Omit any of the four subsections with no entries
330
+ this hop — most re-ratifications touch one or two operations, not all four.
331
+
332
+ **`Renamed` is a requirement-level operation, not a slice-level one.** It records an invariant or
333
+ requirement *within this slice* being renamed or renumbered (e.g. `INV-CHK-3` → `INV-CHK-3a`) —
334
+ it is not a substitute for the `split-from`/`merged-from`/`superseded-by` lineage keys above,
335
+ which record the slice-doc-level operation of one doc splitting, merging, or being renamed into
336
+ another. `em diff`'s own `ChangeType` deliberately treats a slice/element rename as remove+add,
337
+ never a first-class rename (`src/model/diff.ts`: "a rename IS a model change") — `Renamed` here
338
+ is a finer grain, inside one otherwise-unchanged slice, and doesn't revisit that decision.
339
+
340
+ **Lifecycle: replace, never accumulate — the section is a window onto the latest hop, not a
341
+ scroll of every hop.** A slice's first version (`v1`) carries no `## Delta` section at all —
342
+ absence means no hop has happened yet, not an empty section. Ratifying `v2` writes the section
343
+ for the first time, in the same commit that bumps `version` and flips `status`. Ratifying `v3`
344
+ **overwrites** it with the `v2 → v3` hop; the `v1 → v2` content is not retained in the doc.
345
+ Full history is git's job — the same-commit ratification convention above makes `git log -p
346
+ slices/<name>.md` a precise, zero-maintenance delta archive — and a rolled-up multi-hop view, if
347
+ ever wanted, is a *rendering* (e.g. a future `em changelog`), never hand-maintained doc content.
348
+ An accumulating in-doc log would be a second history that diverges from git, grows unboundedly
349
+ over a slice's life, references requirements that have since moved (a *live* doc quietly going
350
+ stale reads as current, unlike git which is honestly historical), and forces every reader to
351
+ work out which delta is the live one — exactly the ambiguity `driftSignal` (MIL-85, below) is
352
+ built to avoid.
353
+
354
+ ## Invariants
355
+
356
+ Since MIL-265 the **model is the preferred place to declare an invariant**: an `invariant
357
+ INV-<MNEMONIC>-<n> "rule"` line after the command or event it guards (see
358
+ [dsl.md](dsl.md#invariants)) carries the ID and the one-line rule sentence. The doc's
359
+ `## Invariants / Business Rules` section then **cites** each model-declared ID and elaborates —
360
+ why the rule exists, what a violation looks like, edge cases, the error returned:
361
+
362
+ ```markdown
363
+ ## Invariants / Business Rules
364
+ - INV-ORD-1 — a zero-total cart is rejected before payment is attempted
365
+ - the check runs on the server; the UI's own check is a convenience only
366
+ ```
367
+
368
+ Write the citation as a plain mention (`- INV-ORD-1 — …`), not with the declaring label
369
+ `**INV-ORD-1:**` — restating a model-declared ID with that label is
370
+ `invariants/declared-in-both` (a warning pointing at the migration). Ownership follows the model:
371
+ a model-declared ID belongs to the slice of the element that declares it, for `em coverage` and
372
+ `em query invariant`, whatever any doc says.
373
+
374
+ **Doc declaration is still honoured.** A doc that declares its own invariants
375
+ (`- **INV-CHK-1:** rule`, or a 1.13 doc's bare `- **INV-1:** rule`) keeps working exactly as
376
+ before: for every ID the model does not declare, the doc-body reader (`## Invariants` and
377
+ `## Delta` sections, top-level bullet/subheading lines only — MIL-149/155) is the fallback.
378
+ A doc mentioning an `INV-…` ID that neither the model nor any doc declares gets
379
+ `invariants/doc-cites-undeclared`. See [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#invariants).
380
+
381
+ ## Generated regions and authored sections
382
+
383
+ Since MIL-266 a slice doc has two kinds of section.
384
+
385
+ **Generated regions** restate the model, and `em` writes them. Each is wrapped in a pair of html
386
+ markers; `em slice new --wire` fills them from the `.em`, and `em slice sync <model>.em` refreshes
387
+ them in place after any model edit. Never hand-edit between the markers — change the model and
388
+ re-run `em slice sync`.
389
+
390
+ | Region | Section | Body |
391
+ |---|---|---|
392
+ | `em-slice-command` | `## Command / Input` | `**Command:** \`Name\`` + `\| Field \| Type \| Required \| Rules / Validation \|` (Required is `no` for a `name?: Type` field) |
393
+ | `em-slice-event` | `## Event(s) Emitted` | `**Event:** \`Name\` → context \`Ctx\`` + `\| Field \| Type \| Immutable Fact? \| Source / Notes \|` (Source: the same-slice command field it is copied from, `assigned` for a system-assigned field, else `{{ }}`) |
394
+ | `em-slice-view` | `## Read Model / View` | `- **View:** \`Name\` built from events: …` + `\| Field \| Type \| Source / Notes \|` (Source: the source event carrying the field, `Derived` for a `derived` field, else `{{ }}`) |
395
+ | `em-slice-invariants` | `## Invariants / Business Rules` | one `- **INV-X** — rule` line per model-declared invariant of the slice's commands and events |
396
+
397
+ A slice with several elements of one kind gets one region per element, the name suffixed with
398
+ the element's slug (`em-slice-event-order-placed`). A kind the slice lacks keeps its one region
399
+ with a "has no …" line, so adding the first element later is a plain `em slice sync`. The
400
+ `**Command:**`/`**Event:**`/`- **View:**` marker lines sit inside the regions, so the
401
+ [doc-model consistency](https://github.com/milehimikey/em/blob/main/docs/validation.md#doc-model-consistency) check reads them as before (and,
402
+ inside a generated `em-slice-view` region, now compares the view's field table too).
403
+
404
+ ```markdown
405
+ ## Command / Input
406
+ <!-- GENERATED:em-slice-command:start — generated from the model by `em slice sync`; do not hand-edit -->
407
+ **Command:** `Submit Order`
408
+
409
+ | Field | Type | Required | Rules / Validation |
410
+ |-------|------|----------|--------------------|
411
+ | total | decimal | yes | — |
412
+ | note | text | no | — |
413
+ <!-- GENERATED:em-slice-command:end -->
414
+ ```
415
+
416
+ The invariants list uses `**INV-X** — rule`, not the declaring label `**INV-X:**`, so a
417
+ generated list never trips `invariants/declared-in-both`. Elaborate each ID below the region
418
+ (after the `<!-- elaborate below this list; IDs are declared in the model -->` comment).
419
+
420
+ **Authored sections** are everything else — Intent, Trigger & Actor, Trigger, `**Read by:**`,
421
+ `Consumed by`/freshness, the invariant elaborations, Scenarios, Alternate & Error Flows, NFRs,
422
+ Dependencies, Open Questions. `em` never writes them; `em slice sync` leaves every byte outside
423
+ the region markers untouched (CRLF included).
424
+
425
+ **Scenario grammar (authored, constrained).** Inside `## Scenarios (Given / When / Then)`, each
426
+ case is a block:
427
+
428
+ ```markdown
429
+ ### Scenario: Non-positive total is rejected (INV-CHK-1)
430
+ - **Given:**
431
+ - a basket whose total is 0
432
+ - **When:**
433
+ - the customer submits the order
434
+ - **Then:**
435
+ - the command is rejected with a validation error
436
+ - no event is recorded
437
+ ```
438
+
439
+ A `### Scenario: <title>` heading, then the three column-0 bullets `- **Given:**`, `- **When:**`,
440
+ `- **Then:**`, each with nested ` - ` items (text after the label on the same line counts as a
441
+ first item). `em export --json` publishes the parsed blocks as `slice.doc.scenarios`
442
+ (`{ title, given, when, then }[]`, `null` when the doc has none). A block missing any of the
443
+ three is `slice-doc/structured-section-malformed`, as is a region whose markers don't balance or
444
+ whose table header differs from the template's — a warning in `em validate`, and a blocker
445
+ (`slice-ready-structured-section-malformed`) under `--slice-ready`. See
446
+ [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#structured-sections).
447
+
448
+ **Migration.** Nothing is required: a doc with no region markers and no `### Scenario:` heading
449
+ (every doc written before 1.14) stays valid, is never flagged, and `em slice sync` skips it with a
450
+ note. To adopt regions, re-create the doc with `em slice new "<name>" … --wire <model>.em --force`
451
+ and carry the authored prose across, or paste the marker pairs in by hand and run
452
+ `em slice sync`.
453
+
454
+ ## Open Questions section: lifecycle
455
+
456
+ `## Open Questions` (MIL-87 for the counting mechanics; MIL-156 for this lifecycle) had no
457
+ cross-version story until now: `countOpenQuestions()` counts every GFM task item under the
458
+ heading, but nothing ever pruned a resolved (`- [x]`) one — left unaddressed, the section grows
459
+ by accretion across every re-ratification of a long-lived slice, the same problem `## Delta`
460
+ (above) solves for by replacing its content wholesale each hop.
461
+
462
+ **Decision: prune resolved items on the same commit that ratifies the next version — replace,
463
+ never accumulate, same stance as `## Delta`, applied by hand instead of by section-rewrite.**
464
+ Concretely:
465
+
466
+ - While the *current* version is still being drafted or reviewed, a checked item can stay in the
467
+ doc — it's a visible record of what this round already decided, not yet an accumulation
468
+ problem (a slice's first version, `v1`, commonly ships with every question already resolved;
469
+ see `examples/order-fulfillment/slices/browse-catalog.md`).
470
+ - On the commit that bumps `version` and rewrites `## Delta` for the next hop, prune every
471
+ already-checked item from `## Open Questions` — keep only what's still open (`- [ ]`), plus
472
+ anything new that hop's change surfaced.
473
+ - Full history isn't lost: it lives in git the same way `## Delta`'s superseded hops do —
474
+ `git log -p slices/<name>.md` shows exactly which questions were open, and how they were
475
+ answered, at each prior version.
476
+
477
+ **Why not keep resolved items as a permanent audit trail instead?** Same reasoning `## Delta`'s
478
+ lifecycle section gives: an ever-growing scroll of resolved questions is a second history that
479
+ diverges from git, grows unboundedly over a long-lived slice's life, and forces every reader to
480
+ work out which resolutions are still relevant to the *current* version versus leftover from a
481
+ prior one — exactly the ambiguity a live, current-version-only section avoids.
482
+
483
+ **Not a parsing/grammar change.** `countOpenQuestions()` (`sliceDoc.ts`) and
484
+ `slice-ready-open-questions-unchecked` (`sliceReadyValidate.ts`,
485
+ [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)) are unaffected: both already
486
+ only care about the CURRENT doc's checkbox state, never about how many hops of history the
487
+ section has accumulated. This is purely an authoring convention — same "documented, not
488
+ mechanically enforced" status `## Delta`'s own lifecycle has (no `em` command checks that pruning
489
+ actually happened on re-ratification).
490
+
491
+ ## `status` under re-ratification
492
+
493
+ When a new version is ratified for a slice whose previous version already shipped, `status`
494
+ tracks the **current version's** implementation state, not a running "has this ever shipped"
495
+ flag. Ratifying v2 on an `implemented` slice flips `status` back to `ready-to-implement`, while
496
+ `implementedIn` keeps naming the v1 PR until v2 ships. That deliberate mismatch — version 2,
497
+ implemented-link still pointing at v1's work — is not staleness, it's the **drift signal**: a
498
+ reader, `em export`'s `slice.doc.driftSignal` (`"unpropagated-delta"`, schema `1.5`, MIL-85), and
499
+ the event-modeling skill's `conform` phase all read it the same way — a ratified delta hasn't
500
+ shipped yet, not a fresh finding against the still-live v1 code. `em validate`'s
501
+ frontmatter-coherence check (MIL-85) deliberately never flags this combination — only
502
+ `status: implemented` with no `implementedIn` link at all is checkable incoherence; see
503
+ [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence).
504
+
505
+ **An unshipped version has nothing to flip back** (MIL-258). A ratified doc that has not shipped
506
+ yet (`status: ready-to-implement` with `ratifiedBy:` set) changes the same way: `em slice
507
+ reratify` bumps `version:` and clears `ratifiedBy`/`ratifiedOn`/`reviewedBy`/`reviewedOn` (and `meaningConfirmed`/`contractChange`, MIL-238), but
508
+ leaves `status` at `ready-to-implement` (and any `implementedIn` as it was — an absent one stays
509
+ absent). This is the path for an answered gap mid-build. In both cases the new version is not
510
+ ratified until `em slice ratify --by <name>` records the sign-off. A `ready-to-implement` doc with
511
+ no `ratifiedBy` is awaiting that sign-off, so a second `reratify` refuses rather than
512
+ double-bump; `draft`/`reviewed` docs are not ratified and are simply edited.
513
+
514
+ Pair a re-ratification with a `## Delta` section (see
515
+ [Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle) above) recording
516
+ the ratified change in typed operation blocks, so it's reviewable without opening git.
517
+
518
+ **Mechanically checking the invariant this section describes:** `em ledger` (MIL-89, opt-in —
519
+ see [cli.md#em-ledger-file](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-ledger-file)) checks that `version:` and a slice doc's
520
+ content (body + lineage refs) always change together across two git revisions — a version bump
521
+ with no real content change, or a content change with no version bump, is a ledger bug.
522
+ It **deliberately excludes `status`/`implementedIn`** from what counts as "content" for exactly
523
+ the reason this section explains: a re-ratification legitimately flips `status` and leaves
524
+ `implementedIn` naming prior work, with no version bump of its own — including either field in
525
+ the comparison would flag every ordinary lifecycle transition as a false positive.
526
+
527
+ ## Unknown keys
528
+
529
+ **Captured, never read, never a warning or error.** Real docs in the wild carry extra
530
+ non-canonical keys — `id`, `title`, `model`, `created`, `updated`, and YAML-list-valued keys
531
+ like `upstreamEvents`. Every top-level `key: value` scalar line is captured internally
532
+ regardless of whether it's one of the keys above; only the keys this document defines are ever
533
+ read back out onto `SliceDoc`. A list-valued key's own line (`upstreamEvents:`, value empty) is
534
+ captured with an empty value; its `- item` continuation lines don't match the `key: value` shape
535
+ at all and are simply skipped. Nothing throws, nothing warns — adding a new non-canonical key to
536
+ a doc is always safe.
537
+
538
+ ## `schemaVersion` vs `version` — don't conflate these
539
+
540
+ | | `schemaVersion` | `version` |
541
+ |---|---|---|
542
+ | What it versions | The frontmatter dialect itself | This one slice's ratified definition |
543
+ | Who bumps it | `em` maintainers, when the dialect's canonical keys change | Whoever ratifies a delta on this slice |
544
+ | Cardinality | One value, same across every doc using this dialect | One per slice doc, incrementing per ratified delta |
545
+ | Current value | `1` (unchanged since MIL-86) | Starts at `1` |
546
+ | Read by `em` today | Value: no — captured, not exposed on `SliceDoc`. Presence: required for usable frontmatter | Yes — parsed onto `SliceDoc.version` |
547
+
548
+ ## Legacy status bullet line
549
+
550
+ Docs written before the frontmatter dialect existed may use `- **Status:** ...` instead of
551
+ frontmatter `status:` — still accepted, status only. `version` and the lineage keys have no
552
+ legacy form; they're frontmatter-only from the day they were introduced.
553
+
554
+ ## See also
555
+
556
+ - [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-render-file) — slice status colors
557
+ - [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-catalog-files) — pattern / doc lookup
558
+ - [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name) — `em slice ratify`, the mechanized ratification act (`ratifiedBy`/`ratifiedOn`, schema `1.8`, MIL-165)
559
+ - [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-reratify-file-slice-key) — `em slice reratify`, the mechanized re-ratification version bump/status flip (MIL-161)
560
+ - [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-export-file) — the `em export` join (`slice.pattern`/`slice.doc`, schema `1.4`, MIL-91), including `owner`/`tracking` (schema `1.9`, MIL-171)
561
+ - [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-diff-old-new) — the `em diff` lineage annotation (schema `1.6`, MIL-84)
562
+ - [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage) — `em validate`'s lineage-ref resolution (MIL-84)
563
+ - [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness) — `em validate --slice-ready`'s note-binding gate, including the `covers` cross-binding (MIL-121)
564
+ - [cli.md#em-coverage-file---tests-dir](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-coverage-file---tests-dir) — `em coverage`'s INV-ID extraction, scoped to the Invariants/Delta sections' structural (non-nested) bullet lines (MIL-149/MIL-155/MIL-156)
565
+ - [validation.md#note-binding-mismatch](https://github.com/milehimikey/em/blob/main/docs/validation.md#note-binding-mismatch) — `em validate`'s check for a doc-shaped note that doesn't participate in its slice's binding (MIL-126)
566
+ - [`templates/slice.md`](../templates/slice.md) — the authored template