avouch 0.1.22

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 (54) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +356 -0
  4. package/SPEC.md +379 -0
  5. package/dist/cli/avouch.d.ts +2 -0
  6. package/dist/cli/avouch.js +68 -0
  7. package/dist/cli/check.d.ts +86 -0
  8. package/dist/cli/check.js +333 -0
  9. package/dist/cli/facts.d.ts +3 -0
  10. package/dist/cli/facts.js +157 -0
  11. package/dist/cli/gate.d.ts +3 -0
  12. package/dist/cli/gate.js +149 -0
  13. package/dist/cli/init.d.ts +21 -0
  14. package/dist/cli/init.js +282 -0
  15. package/dist/cli/jsonl.d.ts +5 -0
  16. package/dist/cli/jsonl.js +20 -0
  17. package/dist/cli/query.d.ts +2 -0
  18. package/dist/cli/query.js +138 -0
  19. package/dist/cli/reconcile.d.ts +9 -0
  20. package/dist/cli/reconcile.js +131 -0
  21. package/dist/src/checker.d.ts +3 -0
  22. package/dist/src/checker.js +1136 -0
  23. package/dist/src/contract.d.ts +135 -0
  24. package/dist/src/contract.js +3 -0
  25. package/dist/src/coverage.d.ts +65 -0
  26. package/dist/src/coverage.js +237 -0
  27. package/dist/src/evaluate.d.ts +66 -0
  28. package/dist/src/evaluate.js +535 -0
  29. package/dist/src/expr.d.ts +209 -0
  30. package/dist/src/expr.js +497 -0
  31. package/dist/src/index.d.ts +11 -0
  32. package/dist/src/index.js +11 -0
  33. package/dist/src/ledger.d.ts +96 -0
  34. package/dist/src/ledger.js +241 -0
  35. package/dist/src/markdown.d.ts +66 -0
  36. package/dist/src/markdown.js +701 -0
  37. package/dist/src/patch.d.ts +6 -0
  38. package/dist/src/patch.js +37 -0
  39. package/dist/src/query.d.ts +333 -0
  40. package/dist/src/query.js +739 -0
  41. package/dist/src/reconcile.d.ts +61 -0
  42. package/dist/src/reconcile.js +151 -0
  43. package/dist/src/stage1-check.d.ts +31 -0
  44. package/dist/src/stage1-check.js +329 -0
  45. package/dist/src/touched.d.ts +1 -0
  46. package/dist/src/touched.js +9 -0
  47. package/dist/src/unknown.d.ts +10 -0
  48. package/dist/src/unknown.js +173 -0
  49. package/docs/adapter-guide.md +187 -0
  50. package/docs/markdown-convention.md +635 -0
  51. package/ontology.schema.json +1757 -0
  52. package/package.json +57 -0
  53. package/templates/agents-block.md +12 -0
  54. package/vocab.ttl +524 -0
@@ -0,0 +1,635 @@
1
+ # Avouch Markdown convention 1
2
+
3
+ This document is the grammar of the built-in Markdown adapter (`src/markdown.ts`). The adapter reads facts only in the form that this document describes. A project has two ways to use Avouch with Markdown documents:
4
+
5
+ 1. Write the documents to this convention. Then `avouch check` runs the built-in adapter, with a JSON configuration (`docs/adapter-guide.md` section 7).
6
+ 2. Write an own adapter. Then the grammar of the documents is free. See `docs/adapter-guide.md` sections 1 to 6; an AI agent uses the prompt in "For AI agents" of that guide.
7
+
8
+ An AI that reformats documents can use this document as the target form. Write each fact as one block, as the templates in sections 3 to 10 show.
9
+
10
+ Version rule:
11
+
12
+ - The convention has a version: `1` in this document.
13
+ - The major number changes only when the grammar changes.
14
+ - A backward-compatible relaxation adds a minor number (`1.1`) and keeps the major number.
15
+ - The inventory file of `avouch facts --inventory` has the field `conventionVersion`. Its value is the convention version (now `1`). See `README.md`.
16
+
17
+ Not guaranteed: prose outside the blocks is not checked. The adapter does not read what a sentence says. The checker compares the blocks with the ontology only.
18
+
19
+ The block is the formal form of the fact. Do not state a different version of the same fact in prose.
20
+
21
+ The conventions are fixed in code, not in configuration.
22
+
23
+ ## 1. Contents
24
+
25
+ | Section | Fact | Rule |
26
+ |---|---|---|
27
+ | 2 | General rules: text, fences, generated blocks, headings and anchors, diagnostics | all |
28
+ | 3 | Field list (`text` block) | R2, R6 |
29
+ | 4 | `storeCatalog` | R2 |
30
+ | 5 | `eventCatalog` | R8 |
31
+ | 6 | `permissions` (catalog block and table) | R10 |
32
+ | 7 | `contextMembers` | R5 |
33
+ | 8 | `scenarios` | R9 |
34
+ | 9 | `stateMachines` | R3 |
35
+ | 10 | `transitionTriggers` | R3 |
36
+ | 11 | Change table | |
37
+
38
+ The rule numbers are the rows of the table in `SPEC.md` section 5.
39
+
40
+ ## 2. General rules
41
+
42
+ ### 2.1 Text
43
+
44
+ - A leading byte order mark is removed.
45
+ - CRLF line ends become LF.
46
+
47
+ ### 2.2 Fences and `text` blocks
48
+
49
+ - A line that starts, after at most three spaces, with n >= 3 backticks opens a fence.
50
+ - Only a line of n or more backticks (after at most three spaces) followed by nothing but white space closes the fence. A three-backtick line inside a four-backtick fence does not close it.
51
+ - `~~~` fences are not supported.
52
+ - An info string that contains backticks is not supported.
53
+ - A text block is a top-level fence whose info string is exactly `text`. Its body runs to its matching closing line.
54
+ - A text block inside another fence is body text. It is not a text block.
55
+ - A fence that is still open at the end of a document is an R2 diagnostic `markdown_fence` (key `doc:<path>`).
56
+
57
+ ### 2.3 Generated blocks
58
+
59
+ - Outside fences, a line that, trimmed, starts with `<!-- BEGIN GENERATED` or `<!-- END GENERATED` is a marker.
60
+ - The marker must be `<!-- BEGIN GENERATED: <name><any note> -->` or `<!-- END GENERATED: <name> -->`. The name has letters, digits, `.`, `_` and `-`.
61
+ - The adapter removes each block with its marker lines before it reads any fact.
62
+ - A comment such as `<!-- GENERATED by a site tool -->` is not a marker.
63
+ - A malformed marker, a nested block, an END that does not match the open block, or a block still open at the end is an R2 diagnostic `markdown_generated_blocks` (key `doc:<path>`). The adapter then reads the document as it is.
64
+ - These diagnostics are R2. With R2 disabled they are dropped, and the adapter reads the raw text, generated content included. Do not disable R2 for a document with generated blocks.
65
+ - A pipe table does not run across a removed generated block.
66
+
67
+ ### 2.4 Headings, sections and anchors
68
+
69
+ - A heading is an ATX heading, `#` to `######`, then a space and a title, outside fences.
70
+ - Setext headings and `{#id}` anchors are not supported.
71
+ - A section runs from its heading to the next heading of the same or a higher level. A section includes its subsections.
72
+ - Each heading has two anchors:
73
+ - the short anchor: the first word of the title, without one trailing `.` (`## 3.2. Pricing` gives `3.2`; `## L1 Book` gives `L1`);
74
+ - the full-title anchor: the whole title, without closing `#` characters (`## Open a rental` gives `Open a rental`).
75
+ - An anchor of two or more sections, in one document or across documents, is `"ambiguous"`. Give each cited heading a unique anchor. Rename a repeated heading first.
76
+ - A heading whose full title equals the first word of another heading also makes that anchor ambiguous. With `## Book` and `## Book details`, the anchor `Book` is ambiguous, and the one-word heading has no unique anchor. Rename one of them.
77
+ - An anchor of no section is `"missing"`.
78
+ - An anchor that starts with `§` and has no section as written is looked up again without the `§`. With `## 6.4 Pricing`, both `§6.4` and `6.4` find it.
79
+ - A heading whose anchor includes the `§` (`## §6.4 Pricing`) keeps it. `§6.4` finds that heading, and `6.4` finds a heading `## 6.4 ...`.
80
+ - The fallback adds no anchor, so an anchor stays unique.
81
+ - A fact keeps the anchor as configured or cited. An R10 cite must still equal the configured `facts.permissions` anchor exactly. `§6.4` and `6.4` are not equal.
82
+
83
+ ### 2.5 Document terms
84
+
85
+ `containsTerm(term)`: the term as a whole identifier in any document, with generated blocks removed. The checker uses it to test whether an action id appears in the documents.
86
+
87
+ ### 2.6 Diagnostics
88
+
89
+ - A configured anchor that is missing or ambiguous is a diagnostic of kind `markdown_anchor` under the rule of the fact. The key is `facts.<name>` (`facts.stateMachines.<Object>` for a state machine). The message is `anchor <anchor> of <key> is missing` or `anchor <anchor> of <key> is ambiguous`. The fact is still supplied, with an empty value. A state machine has the value `null`, so its graph is not compared.
90
+ - Any other parse problem is a diagnostic of kind `markdown_parse` with the same key. Sections 4 to 10 list them.
91
+ - A fence that is not closed: `markdown_fence`, message `<path>: a fence is not closed at the end of the document`.
92
+ - A generated-block problem: `markdown_generated_blocks`, message `<path>: ` and one of these:
93
+ - `line <n>: malformed generated-block marker: <marker>`
94
+ - `line <n>: generated block <name> opens inside <open>`
95
+ - `line <n>: generated block end <name> does not close <open>` (or `an open block`)
96
+ - `generated block <name> is not closed`
97
+ - A diagnostic fails the check like a violation.
98
+ - `avouch facts` prints each diagnostic as `DIAG <rule> <kind> <key>: <msg>`. The text under "Common errors" is `<msg>`.
99
+
100
+ Not guaranteed: the adapter reads only the state machines listed in `facts.stateMachines`. A state machine in a document that the configuration does not list is not checked.
101
+
102
+ ## 3. Field list (`text` block)
103
+
104
+ ### Purpose
105
+
106
+ R2 checks that each property of an object type is a field line of the field list (`property_not_field`), and that the section has one field list for the object (`field_list_missing`). R6 reads the same fields for its link checks (link effects, link catalog, non-link fields).
107
+
108
+ ### Configuration
109
+
110
+ There is no entry in `facts`. The field `doc` of the object type in the ontology names the section (`objectTypes.<Object>.doc`).
111
+
112
+ ### Template
113
+
114
+ ````markdown
115
+ ## <anchor> <Object>
116
+
117
+ ```text
118
+ <Object>
119
+ - <field>
120
+ - <field>?
121
+ - <field_a> / <field_b>
122
+ - <field> // comment
123
+ ```
124
+ ````
125
+
126
+ ### Example
127
+
128
+ From `examples/library/library.md`:
129
+
130
+ ````markdown
131
+ ## L1 Book
132
+
133
+ A Book is one physical copy in the catalog.
134
+
135
+ ```text
136
+ Book
137
+ - id
138
+ - branch_id
139
+ - title
140
+ - state
141
+ ```
142
+
143
+ The title of a Book is set when the copy is catalogued.
144
+ The state of a Book is AVAILABLE, ON_LOAN or WITHDRAWN.
145
+ ````
146
+
147
+ ### Rules
148
+
149
+ - The section has exactly one `text` block whose first line, trimmed, is the object type. Another `text` block with a different first line is allowed. Not exactly one such block: the field list is `null`.
150
+ - Each `- <field>` line is a field. A line that does not start with `- ` is not a field.
151
+ - `- a / b` lists two fields (` / `: a space, a slash and a space).
152
+ - A comment (two slashes to the end of the line) is dropped.
153
+ - A trailing `?` marks the field nullable.
154
+ - A field that is listed twice is nullable when any of its lines is.
155
+
156
+ ### Where prose goes
157
+
158
+ Put prose before or after the block, in the same section. Do not put prose in the block. A line in the block that is not a field line is ignored by the checker. For such a line that is not empty and not a `//` comment, `avouch facts` prints a `WARN` line.
159
+
160
+ ### Common errors
161
+
162
+ | Error | Diagnostic |
163
+ |---|---|
164
+ | No `text` block with the object type as first line, or two such blocks | R2 `field_list_missing`: `<doc> has no single field list headed <Object>` |
165
+ | A property of the ontology has no field line | R2 `property_not_field`: `property <p> is not a field line (- <p>) of the <Object> field list in <doc>` |
166
+ | A line in the block is not a field line | `avouch facts` warning: `WARN <doc>:<line>: <anchor>#<first line>: line is not a field line: <text>` |
167
+ | Two blocks have the same section anchor and first line | `avouch facts` warning: `WARN <doc>:<line>: <anchor>#<first line>: another text block has the same section anchor and first line; this one is not in the fields` |
168
+
169
+ ## 4. `storeCatalog`
170
+
171
+ ### Purpose
172
+
173
+ R2 checks that each store name in the ontology is in the store catalog.
174
+
175
+ ### Configuration
176
+
177
+ `facts.storeCatalog` is the anchor of the section that holds the catalog. In the toy library: `"storeCatalog": "L8"`.
178
+
179
+ ### Template
180
+
181
+ ````markdown
182
+ ## <anchor> Stores
183
+
184
+ ```text
185
+ <store>
186
+ <store> // comment
187
+ <store_a> / <store_b>
188
+ ```
189
+ ````
190
+
191
+ ### Example
192
+
193
+ From `examples/library/library.md`:
194
+
195
+ ````markdown
196
+ ## L8 Stores
197
+
198
+ ```text
199
+ books
200
+ members
201
+ loans
202
+ branches
203
+ people
204
+ audit_log
205
+ ```
206
+
207
+ Every action writes one row to audit_log.
208
+ ````
209
+
210
+ ### Rules
211
+
212
+ - The section has exactly one `text` block. A text block in a subsection counts toward that one.
213
+ - Each line is a list of names. The adapter removes the comment (two slashes to the end of the line) and trims the line. An empty line is skipped.
214
+ - ` / ` (a space, a slash and a space) separates names, as in a field list. Each name is trimmed: `a / b` is the names `a` and `b`.
215
+ - Any other `/` is part of a name: `a/b` is one name.
216
+ - A name is not empty and has no white space.
217
+
218
+ ### Where prose goes
219
+
220
+ Put prose before or after the block, in the same section. Do not put prose in the block, because each line of the block is a list of names. Do not add a second `text` block to the section or to its subsections.
221
+
222
+ ### Common errors
223
+
224
+ | Error | Diagnostic |
225
+ |---|---|
226
+ | Zero or two or more `text` blocks | R2 `markdown_parse` `facts.storeCatalog`: `facts.storeCatalog needs exactly one text code block in its section, found <n>` |
227
+ | An empty name or white space in a name (`a b`, `a / b /`, `a / / b`) | R2 `markdown_parse` `facts.storeCatalog`: `facts.storeCatalog: catalog line has an empty name or white space in a name: <line>` |
228
+ | The anchor is missing or ambiguous | R2 `markdown_anchor` `facts.storeCatalog`: `anchor <anchor> of facts.storeCatalog is missing` (or `is ambiguous`) |
229
+
230
+ ## 5. `eventCatalog`
231
+
232
+ ### Purpose
233
+
234
+ R8 checks that each event that an action emits is in the event catalog.
235
+
236
+ ### Configuration
237
+
238
+ `facts.eventCatalog` is the anchor of the section that holds the catalog. In the toy library: `"eventCatalog": "L9"`.
239
+
240
+ ### Template
241
+
242
+ ````markdown
243
+ ## <anchor> Events
244
+
245
+ ```text
246
+ <event>
247
+ <event> // comment
248
+ <event_a> / <event_b>
249
+ ```
250
+ ````
251
+
252
+ ### Example
253
+
254
+ From `examples/library/library.md`:
255
+
256
+ ````markdown
257
+ ## L9 Events
258
+
259
+ ```text
260
+ loan.created
261
+ loan.returned
262
+ ```
263
+ ````
264
+
265
+ ### Rules
266
+
267
+ - The section has exactly one `text` block. A text block in a subsection counts toward that one.
268
+ - Each line is a list of names. The adapter removes the comment (two slashes to the end of the line) and trims the line. An empty line is skipped.
269
+ - ` / ` (a space, a slash and a space) separates names. Each name is trimmed.
270
+ - Any other `/` is part of a name: `a/b` is one name.
271
+ - A name is not empty and has no white space.
272
+
273
+ ### Where prose goes
274
+
275
+ Put prose before or after the block, in the same section. Do not put prose in the block. Do not add a second `text` block to the section or to its subsections.
276
+
277
+ ### Common errors
278
+
279
+ | Error | Diagnostic |
280
+ |---|---|
281
+ | Zero or two or more `text` blocks | R8 `markdown_parse` `facts.eventCatalog`: `facts.eventCatalog needs exactly one text code block in its section, found <n>` |
282
+ | An empty name or white space in a name | R8 `markdown_parse` `facts.eventCatalog`: `facts.eventCatalog: catalog line has an empty name or white space in a name: <line>` |
283
+ | The anchor is missing or ambiguous | R8 `markdown_anchor` `facts.eventCatalog`: `anchor <anchor> of facts.eventCatalog is missing` (or `is ambiguous`) |
284
+
285
+ ## 6. `permissions`
286
+
287
+ ### Purpose
288
+
289
+ R10 checks that the permission keys of each action in the ontology equal the keys in the source row of the action, that each key is in the permission catalog, and that each permission cite names the configured section and quotes the row.
290
+
291
+ ### Configuration
292
+
293
+ `facts.permissions` is the anchor of the section that holds the catalog block and the table. In the toy library: `"permissions": "L10"`. The configuration sets `requiredSource.R10` to this anchor. A permission cite in the ontology must name this anchor exactly.
294
+
295
+ ### Template
296
+
297
+ ````markdown
298
+ ## <anchor> Permissions
299
+
300
+ ```text
301
+ <permission key>
302
+ <permission key> // comment
303
+ ```
304
+
305
+ | Action | Keys |
306
+ |---|---|
307
+ | <ACTION_ID> | `<permission key>` |
308
+ | <ACTION_ID>, <ACTION_ID> | `<permission key>`, `<permission key>` |
309
+ ````
310
+
311
+ ### Example
312
+
313
+ From `examples/library/library.md`:
314
+
315
+ ````markdown
316
+ ## L10 Permissions
317
+
318
+ ```text
319
+ loan:create
320
+ loan:close
321
+ ```
322
+
323
+ | Action | Keys |
324
+ |---|---|
325
+ | BORROW | `loan:create` |
326
+ | RETURN | `loan:close` |
327
+ ````
328
+
329
+ ### Rules
330
+
331
+ Catalog block:
332
+
333
+ - The section has exactly one `text` block. A text block in a subsection counts toward that one.
334
+ - Each line is a list of names, with the same rules as the store catalog (section 4).
335
+
336
+ Table:
337
+
338
+ - The section has exactly one pipe table outside fences. A pipe table line starts with `|`.
339
+ - The adapter drops the alignment row. A data row made only of dashes, such as `| - | - |`, is read as an alignment row and dropped.
340
+ - The trailing `|` is optional.
341
+ - `\|` and a `|` inside a code span do not split cells.
342
+ - The header has two columns. The text of the header is free.
343
+ - Each data row has two cells.
344
+ - The first cell lists the row names, split on `,` and on `、` (U+3001).
345
+ - The keys are the code spans in the second cell. The second cell is the row text.
346
+ - A row name is listed once. If a name is listed twice, the first row is kept.
347
+ - A quote names a key when it contains the key in backticks.
348
+ - A permission cite must name the configured `facts.permissions` anchor exactly. A full-title anchor of the same section fails.
349
+
350
+ ### Where prose goes
351
+
352
+ Put prose before or after the block and the table, in the same section. Do not put prose in the catalog block. Do not start a prose line with `|`, because the adapter reads it as a table row.
353
+
354
+ ### Common errors
355
+
356
+ | Error | Diagnostic |
357
+ |---|---|
358
+ | Zero or two or more `text` blocks | R10 `markdown_parse` `facts.permissions`: `facts.permissions needs exactly one text code block in its section, found <n>` |
359
+ | An empty name or white space in a name of the catalog | R10 `markdown_parse` `facts.permissions`: `facts.permissions: catalog line has an empty name or white space in a name: <line>` |
360
+ | Zero or two or more tables | R10 `markdown_parse` `facts.permissions`: `facts.permissions needs exactly one pipe table in its section, found <n>` |
361
+ | The header does not have two columns | R10 `markdown_parse` `facts.permissions`: `facts.permissions: the table header has <n> columns, expected 2` |
362
+ | A row does not have two cells | R10 `markdown_parse` `facts.permissions`: `facts.permissions: row has <n> columns, expected 2: <cells joined by " \| ">` |
363
+ | A row name is listed twice | R10 `markdown_parse` `facts.permissions`: `facts.permissions: row <name> is listed more than once; the first row is kept` |
364
+ | The anchor is missing or ambiguous | R10 `markdown_anchor` `facts.permissions`: `anchor <anchor> of facts.permissions is missing` (or `is ambiguous`) |
365
+ | A permission cite names the full-title anchor, or a different anchor, or quotes text that is not in the row | R10 `permission_cite_mismatch`: `permission cite must quote the <ID> row of the source permission table` |
366
+
367
+ ## 7. `contextMembers`
368
+
369
+ ### Purpose
370
+
371
+ R5 checks that the context of each object type in the ontology is the context that the source gives it, and that each entity that the source lists is an object type or is declared out of scope.
372
+
373
+ ### Configuration
374
+
375
+ `facts.contextMembers` is the anchor of the section that holds the lines. In the toy library: `"contextMembers": "L7"`.
376
+
377
+ ### Template
378
+
379
+ ````markdown
380
+ ## <anchor> Contexts
381
+
382
+ - <Context>: <Object>, <Object>
383
+ - <Context>: <Object>
384
+ ````
385
+
386
+ ### Example
387
+
388
+ From `examples/library/library.md`:
389
+
390
+ ````markdown
391
+ ## L7 Contexts
392
+
393
+ - Catalog: Book
394
+ - Circulation: Member, Loan, Branch, Person
395
+ ````
396
+
397
+ ### Rules
398
+
399
+ - Each line `- <context>: <member>, <member>` outside fences is one context. The line starts with `- ` at the start of the line.
400
+ - The context is the text before the first `:`, trimmed. The members are split on `,` and trimmed. An empty member is dropped.
401
+ - A context is listed once.
402
+ - The section has at least one such line.
403
+
404
+ ### Where prose goes
405
+
406
+ Put prose before or after the lines, in the same section. Do not put prose in a line. Do not start a prose line with `- <text>: `, because the adapter reads it as a context line.
407
+
408
+ ### Common errors
409
+
410
+ | Error | Diagnostic |
411
+ |---|---|
412
+ | No context line | R5 `markdown_parse` `facts.contextMembers`: ``facts.contextMembers: the section has no `- <context>: <members>` line`` |
413
+ | A context is listed twice | R5 `markdown_parse` `facts.contextMembers`: `facts.contextMembers: context <context> is listed more than once` |
414
+ | The anchor is missing or ambiguous | R5 `markdown_anchor` `facts.contextMembers`: `anchor <anchor> of facts.contextMembers is missing` (or `is ambiguous`) |
415
+
416
+ ## 8. `scenarios`
417
+
418
+ ### Purpose
419
+
420
+ R9 checks that every scenario that an action lists exists, that the scenario cites the action, and that every action is cited by a scenario.
421
+
422
+ ### Configuration
423
+
424
+ `facts.scenarios` is the anchor of the section that holds the scenario lines. In the toy library: `"scenarios": "L11"`. Without this entry, R9 is off.
425
+
426
+ ### Template
427
+
428
+ ````markdown
429
+ ## <anchor> Scenarios
430
+
431
+ - <id>: <text> (<ACTION_ID>).
432
+ - <id>: <text> (<ACTION_ID>, <ACTION_ID>).
433
+ ````
434
+
435
+ ### Example
436
+
437
+ From `examples/library/library.md`:
438
+
439
+ ````markdown
440
+ ## L11 Scenarios
441
+
442
+ - S1: a member borrows an AVAILABLE book (BORROW).
443
+ - S2: a member returns a book (RETURN).
444
+ ````
445
+
446
+ ### Rules
447
+
448
+ - Each line `- <id>: <text>` outside fences in the section is one scenario.
449
+ - The id is one word, without white space, before the first `: `. A line such as `- Happy path: ...` is not a scenario.
450
+ - The scenario cites the action ids in the last bracket pair at the end of the line. The adapter removes white space and one trailing `.` or `。`. Then it reads the text inside a closing `(...)` or `(...)` (the two brackets of the same kind), splits it on `,` and `,`, and trims each item.
451
+ - `- S1: an AVAILABLE book (BORROW).` cites `BORROW` only. Other upper-case words in the text are not cited.
452
+ - A line without a closing bracket pair cites nothing.
453
+ - Put only action ids in that last bracket pair. `- S1: borrow a book (members only).` is an error.
454
+ - An action id cannot contain a bracket.
455
+ - Each item must match `actionIdPattern`. An item that does not match is not cited.
456
+ - A scenario id is listed once. If an id is listed twice, the first line is kept.
457
+ - The section has at least one scenario line.
458
+
459
+ ### Where prose goes
460
+
461
+ Put prose before or after the lines, in the same section. Do not put prose in a scenario line after the last bracket pair. Do not start a prose line with `- <word>: `, because the adapter reads it as a scenario.
462
+
463
+ ### Common errors
464
+
465
+ | Error | Diagnostic |
466
+ |---|---|
467
+ | An item in the last bracket pair does not match `actionIdPattern` (for example `members only`) | R9 `markdown_parse` `facts.scenarios`: `facts.scenarios: scenario <id> cites "<item>", which does not match actionIdPattern` |
468
+ | A scenario id is listed twice | R9 `markdown_parse` `facts.scenarios`: `facts.scenarios: scenario <id> is listed more than once; the first is kept` |
469
+ | No scenario line | R9 `markdown_parse` `facts.scenarios`: ``facts.scenarios: the section has no `- <id>: <text> (<action>, ...)` line`` |
470
+ | The anchor is missing or ambiguous | R9 `markdown_anchor` `facts.scenarios`: `anchor <anchor> of facts.scenarios is missing` (or `is ambiguous`) |
471
+
472
+ ## 9. `stateMachines`
473
+
474
+ ### Purpose
475
+
476
+ R3 checks that the state graph of each object type in the ontology equals the state diagram in the source: states, initial states and edges.
477
+
478
+ ### Configuration
479
+
480
+ `facts.stateMachines` is an object. Each key is an object type, and each value is the anchor of the section that holds the state diagram of that object. In the toy library: `"stateMachines": {"Book": "L4", "Loan": "L5"}`. Set `facts.stateMachines` and `facts.transitionTriggers` together or not at all. Without them, R3 is off.
481
+
482
+ ### Template
483
+
484
+ ````markdown
485
+ ## <anchor> <Object> State
486
+
487
+ ```plantuml
488
+ @startuml
489
+ [*] --> <INITIAL_STATE>
490
+ <STATE_A> --> <STATE_B> : <ACTION_ID>
491
+ <STATE_B> --> <STATE_C>
492
+ @enduml
493
+ ```
494
+ ````
495
+
496
+ ### Example
497
+
498
+ From `examples/library/library.md`:
499
+
500
+ ````markdown
501
+ ## L4 Book State
502
+
503
+ ```plantuml
504
+ @startuml
505
+ [*] --> AVAILABLE
506
+ AVAILABLE --> ON_LOAN : BORROW
507
+ ON_LOAN --> AVAILABLE : RETURN
508
+ AVAILABLE --> WITHDRAWN
509
+ @enduml
510
+ ```
511
+
512
+ A librarian withdraws a copy outside the system.
513
+ ````
514
+
515
+ ### Rules
516
+
517
+ - For each configured object type, the section has exactly one state diagram block. It is a top-level fence whose info string is `plantuml`. It can also be a fence whose info string is `mermaid` and whose first line that is not empty and not a `%%` comment is `stateDiagram` or `stateDiagram-v2`.
518
+ - Each body line, trimmed, is one of the forms below. Any other line is an error. Examples of other lines: `state X {`, `A -> B`, `state "x" as X`, a `<<choice>>` state.
519
+ - These lines are ignored:
520
+ - an empty line;
521
+ - `@startuml`, `@enduml`, `stateDiagram`, `stateDiagram-v2`;
522
+ - a line that starts with `'` or `%%`;
523
+ - a line that starts with `hide `, `skinparam `, `title ` or `direction `;
524
+ - a one-line note (`note ` ... `:` ...);
525
+ - a note block. It runs from a line `note left of <state>` (or `right`, `top`, `bottom`) to a line `end note`.
526
+ - Any other line that starts with `note `, for example `note "x" as N1`, is outside the grammar.
527
+ - An edge is `<from> --> <to>` or `<from> --> <to> : <label>`. The spaces around `-->` and before `:` are optional, so `A-->B` and the mermaid form `A --> B: label` are edges.
528
+ - `<from>` and `<to>` are `[*]` or a name of letters, digits and `_` that does not start with a digit.
529
+ - `[*] --> X` makes X an initial state and a state.
530
+ - `X --> [*]` makes X a state and no edge.
531
+ - `A --> B` makes A and B states and the edge `A->B`. An edge that is listed twice is kept once.
532
+ - The value is `{nodes, initial, edges}`. Each list is in the order of first appearance. The fact has one entry for each configured object type, with the configured anchor as its source.
533
+ - The adapter reads only the state machines listed in `facts.stateMachines`. A state machine in a document that the configuration does not list is not checked.
534
+
535
+ ### Where prose goes
536
+
537
+ Put prose before or after the diagram, in the same section. Do not put prose in the diagram, except as a `note`. Do not add a second `plantuml` block, or a second `mermaid` state diagram block, to the section or to its subsections.
538
+
539
+ ### Common errors
540
+
541
+ All diagnostics are rule R3, kind `markdown_parse`, key `facts.stateMachines.<Object>`. The value of the state machine is `null`.
542
+
543
+ | Error | Diagnostic |
544
+ |---|---|
545
+ | Zero or two or more state diagram blocks | `facts.stateMachines.<Object> needs exactly one state diagram block (plantuml, or mermaid stateDiagram) in its section, found <n>` |
546
+ | A line outside the grammar | `state diagram line is outside the grammar: <line>` |
547
+ | A note block without `end note` | ``state diagram: a `note` block is not closed by `end note` `` |
548
+ | The anchor is missing or ambiguous (kind `markdown_anchor`) | `anchor <anchor> of facts.stateMachines.<Object> is missing` (or `is ambiguous`) |
549
+
550
+ ## 10. `transitionTriggers`
551
+
552
+ ### Purpose
553
+
554
+ R3 checks that the `by` list of each transition in the ontology equals the triggers in the source table.
555
+
556
+ ### Configuration
557
+
558
+ `facts.transitionTriggers` is the anchor of the section that holds the table. In the toy library: `"transitionTriggers": "L6"`. Set it together with `facts.stateMachines`.
559
+
560
+ ### Template
561
+
562
+ ````markdown
563
+ ## <anchor> Actions
564
+
565
+ | Object | Transition | Trigger |
566
+ |---|---|---|
567
+ | <Object> | <STATE_A> → <STATE_B> | <ACTION_ID> |
568
+ | <Object> | <STATE_A> / <STATE_B> → <STATE_C>, <STATE_C> → <STATE_A> | <ACTION_ID>, <ACTION_ID> |
569
+ ````
570
+
571
+ ### Example
572
+
573
+ From `examples/library/library.md`:
574
+
575
+ ````markdown
576
+ ## L6 Actions
577
+
578
+ | Object | Transition | Trigger |
579
+ |---|---|---|
580
+ | Book | AVAILABLE → ON_LOAN | BORROW |
581
+ | Book | ON_LOAN → AVAILABLE | RETURN |
582
+ | Loan | ACTIVE → RETURNED | RETURN |
583
+
584
+ BORROW creates a Loan for an AVAILABLE Book and a Member who is not blocked.
585
+ RETURN closes an ACTIVE Loan and makes the Book AVAILABLE again.
586
+ BORROW and RETURN write Book and Member in the same transaction as the Loan.
587
+ BORROW takes member_id, the Member who borrows; only that Member may submit BORROW.
588
+ BORROW takes book_id, the Book to lend.
589
+ RETURN takes loan_id, the Loan to close.
590
+ BORROW and RETURN check their rules from top to bottom; the first rule that matches decides.
591
+ BORROW by anyone other than that Member is REJECTED. BORROW of a Book that is not AVAILABLE is REJECTED. BORROW by a blocked Member is REJECTED. BORROW by a Member with a Loan that is not returned in GOOD condition is REFERRED to a librarian. Otherwise BORROW is BORROWED.
592
+ RETURN of a Loan that is not ACTIVE is REJECTED. RETURN after the due_date, on the date of the Branch timezone, is RETURNED_LATE. Otherwise RETURN is RETURNED.
593
+ The caller of an action is a Person.
594
+ <!-- BEGIN GENERATED: action-summary -->
595
+ Generated from the ontology: BORROW edits Book.state and Member.open_loans.
596
+ <!-- END GENERATED: action-summary -->
597
+ ````
598
+
599
+ The generated block is removed before the adapter reads the section (section 2.3).
600
+
601
+ ### Rules
602
+
603
+ - The section has exactly one pipe table outside fences, with the table rules of section 6 (alignment row, optional trailing `|`, `\|`).
604
+ - The header has exactly three columns: object, transition, trigger. The text of the header is free.
605
+ - Each data row has three cells. A row with another number of cells is skipped.
606
+ - The object is the first cell, trimmed. It must be a key of `facts.stateMachines`. Else the row binds nothing.
607
+ - The transition cell is a list split on `,` and `,`. Each item is `<state> → <state>` or `<state> -> <state>`. Each side can list states with ` / `: `PENDING / ACCEPTED → STARTED` is the edges `PENDING->STARTED` and `ACCEPTED->STARTED`.
608
+ - The trigger cell is a list of action ids split on `,` and `,`. Each item must match `actionIdPattern`.
609
+ - An item that does not fit is an error, and the whole row binds nothing.
610
+ - When two rows give the same object and edge, the triggers are joined.
611
+ - The adapter does not read the ontology, so it keeps a trigger that the ontology does not declare. R3 reports it.
612
+
613
+ ### Where prose goes
614
+
615
+ Put prose before or after the table, in the same section. Do not put prose in a cell. Do not start a prose line with `|`, because the adapter reads it as a table row.
616
+
617
+ ### Common errors
618
+
619
+ All diagnostics are rule R3, kind `markdown_parse`, key `facts.transitionTriggers`.
620
+
621
+ | Error | Diagnostic |
622
+ |---|---|
623
+ | Zero or two or more tables | `facts.transitionTriggers needs exactly one pipe table in its section, found <n>` |
624
+ | The header does not have three columns | `facts.transitionTriggers: the table header has <n> columns, expected 3` |
625
+ | A row does not have three cells | `facts.transitionTriggers: row has <n> columns, expected 3: <cells joined by " \| ">` |
626
+ | The object is not a key of `facts.stateMachines` | `facts.transitionTriggers: object "<object>" is not a key of facts.stateMachines: <cells joined by " \| ">` |
627
+ | An item of the transition cell is not a transition | ``facts.transitionTriggers: "<item>" is not a transition `<state> → <state>` `` |
628
+ | A trigger does not match `actionIdPattern` | `facts.transitionTriggers: trigger "<item>" does not match actionIdPattern` |
629
+ | The anchor is missing or ambiguous (kind `markdown_anchor`) | `anchor <anchor> of facts.transitionTriggers is missing` (or `is ambiguous`) |
630
+
631
+ ## 11. Change table
632
+
633
+ | Convention version | Package version | Change |
634
+ |---|---|---|
635
+ | 1 | 0.1.21 | First published form. The grammar is the one that `src/markdown.ts` reads. |