@formicoidea/labre-framework-ddd-event-storming 0.32.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legend.js ADDED
@@ -0,0 +1,58 @@
1
+ import { ES_HOTSPOT, ES_STICKIES, LABEL_COLOR, roleLabel, } from '@formicoidea/labre-ddd-shared';
2
+ import { ES_ROLE, ES_STICKY_ROLE, EVENT_STORMING_ROLES } from './roles.js';
3
+ /**
4
+ * What the Event Storming board's automatic legend can say — a TABLE, and
5
+ * nothing else: the scan, the placement and the box are `createAutoLegend`'s
6
+ * job.
7
+ *
8
+ * Every row is DERIVED from the palette the stickies are drawn with
9
+ * ({@link ES_STICKIES}, {@link ES_HOTSPOT}) and from the role vocabulary's own
10
+ * labels, never restated — so the colour ladder the legend shows is by
11
+ * construction the colour ladder on the wall, and a tenth sticky kind added to
12
+ * `ES_STICKIES` gets its legend row with no edit.
13
+ *
14
+ * The hotspot is appended for the same reason `roles.ts` appends it: it lives in
15
+ * its own preset (a diamond, not a square) rather than in the table.
16
+ */
17
+ export const EVENT_STORMING_AUTO_LEGEND = {
18
+ title: 'Legend',
19
+ roles: EVENT_STORMING_ROLES,
20
+ sections: [
21
+ {
22
+ title: 'Stickies',
23
+ entries: [
24
+ ...ES_STICKIES.map(preset => ({
25
+ role: ES_STICKY_ROLE[preset.kind],
26
+ row: {
27
+ swatch: 'square',
28
+ color: preset.fill,
29
+ label: preset.label,
30
+ },
31
+ })),
32
+ {
33
+ role: ES_STICKY_ROLE.hotspot,
34
+ row: {
35
+ swatch: 'square',
36
+ color: ES_HOTSPOT.fill,
37
+ label: ES_HOTSPOT.label,
38
+ },
39
+ },
40
+ ],
41
+ },
42
+ {
43
+ title: 'Flow',
44
+ entries: [
45
+ {
46
+ role: ES_ROLE.flow,
47
+ row: {
48
+ swatch: 'line',
49
+ // The style `activateEventStormingFlow` arms the connector tool
50
+ // with: a solid line in the label colour.
51
+ color: LABEL_COLOR,
52
+ label: roleLabel(EVENT_STORMING_ROLES, ES_ROLE.flow),
53
+ },
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ };
@@ -0,0 +1,26 @@
1
+ import type { QualityNudge } from '@formicoidea/labre-core/blocks/surface';
2
+ /**
3
+ * Event Storming **board quality** — the checklist (WS5, level 2).
4
+ *
5
+ * `./rules.ts` is level 1: deterministic, decidable, real time. This file is
6
+ * everything a board needs that no algorithm can decide. The split is the
7
+ * taxonomy's whole point — ticking is assuming, never verifying.
8
+ *
9
+ * ## The past tense lives HERE, and that was an arbitration
10
+ *
11
+ * "Order placed" is a domain event; "Place order" is a command. It is the first
12
+ * thing a facilitator corrects and the most tempting rule in the whole plan —
13
+ * and it is a nudge, by PO arbitration of 26/08/2026, because deciding it means
14
+ * parsing a human sentence. In whatever language the room speaks. Written in
15
+ * marker pen. By somebody who abbreviates. A regular expression over `Y.Text`
16
+ * would be wrong every fifth sticky, and a validation platform gets exactly one
17
+ * chance to be wrong about something the user can see is right.
18
+ *
19
+ * So it is a tick, and the tick is honest: it says the room LOOKED, which is
20
+ * what the facilitator wanted anyway.
21
+ *
22
+ * Registered from the flag-gated `DddEventStormingViewExtension`: a checklist
23
+ * is tooling. Switching the flag off takes it away and leaves the ticks written
24
+ * on the board, unread, until it comes back.
25
+ */
26
+ export declare const EVENT_STORMING_NUDGES: readonly QualityNudge[];
package/dist/nudges.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Event Storming **board quality** — the checklist (WS5, level 2).
3
+ *
4
+ * `./rules.ts` is level 1: deterministic, decidable, real time. This file is
5
+ * everything a board needs that no algorithm can decide. The split is the
6
+ * taxonomy's whole point — ticking is assuming, never verifying.
7
+ *
8
+ * ## The past tense lives HERE, and that was an arbitration
9
+ *
10
+ * "Order placed" is a domain event; "Place order" is a command. It is the first
11
+ * thing a facilitator corrects and the most tempting rule in the whole plan —
12
+ * and it is a nudge, by PO arbitration of 26/08/2026, because deciding it means
13
+ * parsing a human sentence. In whatever language the room speaks. Written in
14
+ * marker pen. By somebody who abbreviates. A regular expression over `Y.Text`
15
+ * would be wrong every fifth sticky, and a validation platform gets exactly one
16
+ * chance to be wrong about something the user can see is right.
17
+ *
18
+ * So it is a tick, and the tick is honest: it says the room LOOKED, which is
19
+ * what the facilitator wanted anyway.
20
+ *
21
+ * Registered from the flag-gated `DddEventStormingViewExtension`: a checklist
22
+ * is tooling. Switching the flag off takes it away and leaves the ticks written
23
+ * on the board, unread, until it comes back.
24
+ */
25
+ export const EVENT_STORMING_NUDGES = [
26
+ {
27
+ /** See the header: the rule that is deliberately not a rule. */
28
+ id: 'es.q1-events-past-tense',
29
+ framework: 'ddd-event-storming',
30
+ labelKey: 'com.labre.event-storming.quality.events-past-tense',
31
+ fallback: 'Domain events are named in the past tense — something that happened, not something to do.',
32
+ order: 1,
33
+ },
34
+ {
35
+ /**
36
+ * `es.against-timeline` judges each arc against the axis; nothing can judge
37
+ * whether the frieze as a whole tells the story the room means. Reading it
38
+ * out loud, left to right, is the move that finds the missing half of it.
39
+ */
40
+ id: 'es.q2-timeline-read',
41
+ framework: 'ddd-event-storming',
42
+ labelKey: 'com.labre.event-storming.quality.timeline-read',
43
+ fallback: 'The timeline has been read out loud left to right, and reordered where it did not hold.',
44
+ order: 2,
45
+ },
46
+ {
47
+ id: 'es.q3-hotspots-discussed',
48
+ framework: 'ddd-event-storming',
49
+ labelKey: 'com.labre.event-storming.quality.hotspots-discussed',
50
+ fallback: 'Every hotspot has been discussed — resolved, or assumed on purpose with a name against it.',
51
+ order: 3,
52
+ },
53
+ {
54
+ id: 'es.q4-actors-and-systems',
55
+ framework: 'ddd-event-storming',
56
+ labelKey: 'com.labre.event-storming.quality.actors-and-systems',
57
+ fallback: 'The actors and the external systems are identified: every command has someone who issues it.',
58
+ order: 4,
59
+ },
60
+ {
61
+ /**
62
+ * A pivotal event is a marking CONVENTION — a vertical bar, a bigger
63
+ * sticky, a line drawn on the paper — and the canvas cannot tell one from a
64
+ * sticky somebody happened to enlarge. What matters is that the room agreed
65
+ * which moments split the story, which is a conversation and not a shape.
66
+ */
67
+ id: 'es.q5-pivotal-events',
68
+ framework: 'ddd-event-storming',
69
+ labelKey: 'com.labre.event-storming.quality.pivotal-events',
70
+ fallback: 'The pivotal events are marked: the board says where the story changes phase.',
71
+ order: 5,
72
+ },
73
+ ];
@@ -0,0 +1,2 @@
1
+ import type { ValidationProfile } from '@formicoidea/labre-core/blocks/surface';
2
+ export declare const EVENT_STORMING_PROFILES: readonly ValidationProfile[];
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Event Storming validation profiles (WS5).
3
+ *
4
+ * DATA owned by the framework, like its rules and its roles. A profile is
5
+ * chosen per BOARD, not per document, and the choice rides on the board element
6
+ * itself — a Big Picture and a Process-modelling board coexist on one canvas at
7
+ * two levels of requirement, which is precisely how the workshop goes.
8
+ *
9
+ * ## Three, and why three (PO arbitration, 26/08/2026)
10
+ *
11
+ * Every other framework in this library ships two. Event Storming gets three
12
+ * because it is not one activity, it is a SEQUENCE of them, and the same wall
13
+ * means different things at each stage:
14
+ *
15
+ * 1. **Big Picture (Sketch)** — everything on the wall at once, out of order,
16
+ * the grammar not yet decided. The linter must be silent;
17
+ * 2. **Process modelling** — the frieze gets read left to right and reordered.
18
+ * Time starts to mean something; the grammar still does not;
19
+ * 3. **Software design** — the sentences are supposed to be sentences.
20
+ *
21
+ * Two profiles would have forced stage 2 to pick a side: either the timeline
22
+ * goes unchecked through the very session that is about ordering it, or the
23
+ * grammar starts firing at a wall that has not been cleaned up yet. The middle
24
+ * profile exists because the middle stage exists.
25
+ *
26
+ * Registered from the flag-gated `DddEventStormingViewExtension`, beside the
27
+ * rules: switching the flag off takes the choice away with the rest of the
28
+ * tooling, and a board already set to `design` simply stops being checked until
29
+ * it comes back — the id stays written, untouched.
30
+ */
31
+ /**
32
+ * Sketch — the Big Picture. Every rule demoted to `audit`: findings still reach
33
+ * `violations$` for a host panel and a conformance report, and the canvas says
34
+ * nothing.
35
+ *
36
+ * Named **"Big Picture (Sketch)"** in the dropdown, both words on purpose (PO
37
+ * recette, 26/08/2026). "Sketch" is the vocabulary every framework in this
38
+ * library shares for its quietest level, and it is the word the recette script
39
+ * uses; "Big Picture" is the word the workshop uses for this stage and the only
40
+ * one a facilitator would look for. Carrying both is what lets the two readings
41
+ * meet on one entry instead of the user having to learn which name this
42
+ * framework chose.
43
+ *
44
+ * The DEFAULT, and more deliberately here than anywhere else in the library. A
45
+ * Big Picture is **supposed to be chaotic**: that is the method, not a failure
46
+ * of it. Events go up out of order because remembering is not sorting, arcs get
47
+ * drawn between things nobody has typed yet, and stickies get stacked three
48
+ * deep while a stack is what somebody means. A tool arguing with that hand is
49
+ * not strict, it is wrong — it is judging a stage of the workshop by the
50
+ * criteria of a later one.
51
+ *
52
+ * Being the default also means it WRITES NOTHING: a board on `sketch` carries no
53
+ * profile key, so every board ever stormed is on it, with no migration and no
54
+ * backfill.
55
+ */
56
+ const sketch = {
57
+ id: 'es.sketch',
58
+ framework: 'ddd-event-storming',
59
+ labelKey: 'com.labre.event-storming.profile.sketch',
60
+ fallback: 'Big Picture (Sketch)',
61
+ isDefault: true,
62
+ rules: {
63
+ 'es.against-timeline': 'audit',
64
+ 'es.forbidden-arc': 'audit',
65
+ 'es.overlapping-stickies': 'audit',
66
+ },
67
+ };
68
+ /**
69
+ * Process — the frieze is being read left to right and reordered.
70
+ *
71
+ * The timeline is the ONLY thing promoted, because ordering the frieze is
72
+ * exactly what this stage is for: an arc running backwards is now the finding
73
+ * the session is looking for, and the tool pointing at it is the tool helping.
74
+ *
75
+ * The grammar stays at `audit`, on purpose. The wall is still full of stickies
76
+ * whose kind nobody has settled — an event that is going to turn out to be a
77
+ * command, a read model somebody drew as an aggregate — and firing
78
+ * `forbidden-arc` at that is arguing about a sentence whose words are still
79
+ * being chosen. Overlaps stay quiet for the same reason: things are being
80
+ * MOVED, and everything overlaps while it is being moved.
81
+ */
82
+ const process = {
83
+ id: 'es.process',
84
+ framework: 'ddd-event-storming',
85
+ labelKey: 'com.labre.event-storming.profile.process',
86
+ fallback: 'Process modelling',
87
+ rules: {
88
+ 'es.against-timeline': 'warning',
89
+ 'es.forbidden-arc': 'audit',
90
+ 'es.overlapping-stickies': 'audit',
91
+ },
92
+ };
93
+ /**
94
+ * Design — the deliverable level: a board that leaves the room and is read by
95
+ * somebody who was not in it.
96
+ *
97
+ * All three at `warning`. By this stage every sticky has a kind, every arc is a
98
+ * claim about the model, and a frieze nobody can read is a frieze that will be
99
+ * misread. Every severity is spelled out rather than left absent, so the answer
100
+ * is readable in one place.
101
+ *
102
+ * Nothing here is `blocking-overridable`: nothing in this library implements
103
+ * refusal, and declaring it would be data claiming an effect that does not
104
+ * exist.
105
+ */
106
+ const design = {
107
+ id: 'es.design',
108
+ framework: 'ddd-event-storming',
109
+ labelKey: 'com.labre.event-storming.profile.design',
110
+ fallback: 'Software design',
111
+ rules: {
112
+ 'es.against-timeline': 'warning',
113
+ 'es.forbidden-arc': 'warning',
114
+ 'es.overlapping-stickies': 'warning',
115
+ },
116
+ };
117
+ export const EVENT_STORMING_PROFILES = [
118
+ sketch,
119
+ process,
120
+ design,
121
+ ];
@@ -0,0 +1,62 @@
1
+ import { ES_STICKIES } from '@formicoidea/labre-ddd-shared';
2
+ import type { RoleDefs } from '@formicoidea/labre-core/std/gfx';
3
+ /**
4
+ * Event Storming role vocabulary (WS5).
5
+ *
6
+ * A role is the semantic identity of an artefact — no rule will ever look at a
7
+ * shape type or at a fill colour. Three families:
8
+ *
9
+ * - the **board** (`es:board`), the paper roll the storm is run on;
10
+ * - the **sticky** (`es:sticky`), parent of the nine kinds the notation has;
11
+ * - the **flow** (`es:flow`), the edge that says one thing leads to another.
12
+ *
13
+ * ## Why a sticky PARENT
14
+ *
15
+ * Because two of the three rules are written on it and neither wants to know
16
+ * about the nine. `es.overlapping-stickies` is a readability rule — any two
17
+ * stickies on top of each other are unreadable, whichever kinds they are — and
18
+ * the day a tenth kind lands it is covered without a line changing.
19
+ * Specialisation is DATA (`parent`), read by `roleIsA`.
20
+ *
21
+ * ## The alphabet is smaller than the vocabulary
22
+ *
23
+ * All nine kinds get a role; only seven of them appear in the grammar
24
+ * (`es.forbidden-arc`). The **hotspot** and the **constraint** are declared here
25
+ * and cited by no triplet, deliberately: a hotspot is the workshop saying "we do
26
+ * not know", and a flow drawn onto one is somebody parking a question, not
27
+ * making a claim. Being outside the alphabet takes the whole edge out of the
28
+ * conversation (see `RelationEndpointsDef.allowed`), which is exactly the
29
+ * proportionality the family was built for.
30
+ *
31
+ * ## Compat
32
+ *
33
+ * Nothing is backfilled. Stickies placed before these roles existed carry none
34
+ * and are never evaluated (promesse #71): they stay drawings, in the documents
35
+ * they are in.
36
+ */
37
+ /** The sticky kinds as used at the creation sites, hotspot included. */
38
+ export type EventStormingStickyKind = (typeof ES_STICKIES)[number]['kind'] | 'hotspot';
39
+ export type EventStormingRoleId = `es:${string}`;
40
+ /**
41
+ * Role id per sticky kind, DERIVED from the shared preset table rather than
42
+ * restated — so the day a tenth sticky lands in `ES_STICKIES` it gets its role
43
+ * here with no edit. The hotspot is appended because it lives in its own preset
44
+ * (`ES_HOTSPOT`, a diamond rather than a square) and not in the table.
45
+ */
46
+ export declare const ES_STICKY_ROLE: Record<EventStormingStickyKind, EventStormingRoleId>;
47
+ /** The three roles that are not a sticky kind, plus the nine that are. */
48
+ export declare const ES_ROLE: {
49
+ readonly domainEvent: `es:${string}`;
50
+ readonly command: `es:${string}`;
51
+ readonly aggregate: `es:${string}`;
52
+ readonly actor: `es:${string}`;
53
+ readonly constraint: `es:${string}`;
54
+ readonly policy: `es:${string}`;
55
+ readonly readModel: `es:${string}`;
56
+ readonly system: `es:${string}`;
57
+ readonly hotspot: `es:${string}`;
58
+ readonly board: "es:board";
59
+ readonly sticky: "es:sticky";
60
+ readonly flow: "es:flow";
61
+ };
62
+ export declare const EVENT_STORMING_ROLES: RoleDefs;
package/dist/roles.js ADDED
@@ -0,0 +1,84 @@
1
+ import { ES_HOTSPOT, ES_STICKIES } from '@formicoidea/labre-ddd-shared';
2
+ /** camelCase → kebab-case, the WS2 derivation. */
3
+ const kebab = (kind) => kind.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
4
+ /**
5
+ * The one kind whose role id is not its kind kebab-cased.
6
+ *
7
+ * `system` is what the palette has called the sticky since the tool shipped and
8
+ * what its telemetry emits; `external-system` is what Event Storming calls the
9
+ * thing, and it is the word a rule and a hover reveal have to say. Renaming the
10
+ * kind would move a telemetry value (ADR 0008 forbids it) and renaming the role
11
+ * would make the grammar read `command → system`, which is not a sentence
12
+ * anybody says. One entry in a table is the cost of keeping both right.
13
+ */
14
+ const ROLE_SLUG = {
15
+ system: 'external-system',
16
+ };
17
+ const slug = (kind) => ROLE_SLUG[kind] ?? kebab(kind);
18
+ /**
19
+ * Role id per sticky kind, DERIVED from the shared preset table rather than
20
+ * restated — so the day a tenth sticky lands in `ES_STICKIES` it gets its role
21
+ * here with no edit. The hotspot is appended because it lives in its own preset
22
+ * (`ES_HOTSPOT`, a diamond rather than a square) and not in the table.
23
+ */
24
+ export const ES_STICKY_ROLE = Object.fromEntries([
25
+ ...ES_STICKIES.map(preset => [preset.kind, `es:${slug(preset.kind)}`]),
26
+ ['hotspot', 'es:hotspot'],
27
+ ]);
28
+ /** The three roles that are not a sticky kind, plus the nine that are. */
29
+ export const ES_ROLE = {
30
+ board: 'es:board',
31
+ sticky: 'es:sticky',
32
+ flow: 'es:flow',
33
+ ...ES_STICKY_ROLE,
34
+ };
35
+ /** Human label per kind, from the same presets the palette renders. */
36
+ const STICKY_LABEL = {
37
+ ...Object.fromEntries(ES_STICKIES.map(preset => [preset.kind, preset.label])),
38
+ hotspot: ES_HOTSPOT.label,
39
+ };
40
+ const DEFS = [
41
+ // The board: a frame, not a sticky. It specialises nothing, so a rule written
42
+ // on `es:sticky` can never fall on the roll its subjects are stuck to.
43
+ {
44
+ id: ES_ROLE.board,
45
+ kind: 'node',
46
+ labelKey: 'com.labre.event-storming.role.board',
47
+ labelFallback: 'Event Storming board',
48
+ },
49
+ {
50
+ id: ES_ROLE.sticky,
51
+ kind: 'node',
52
+ labelKey: 'com.labre.event-storming.role.sticky',
53
+ labelFallback: 'Sticky',
54
+ },
55
+ ...Object.keys(STICKY_LABEL).map((kind) => ({
56
+ id: ES_STICKY_ROLE[kind],
57
+ parent: ES_ROLE.sticky,
58
+ kind: 'node',
59
+ labelKey: `com.labre.event-storming.role.${slug(kind)}`,
60
+ labelFallback: STICKY_LABEL[kind],
61
+ })),
62
+ /**
63
+ * The flow. Tier 1 of `docs/adr/0010`: the source is the subject of the verb,
64
+ * so the source is what happens FIRST. Everything downstream of that sentence
65
+ * — the tool hint, the hover reveal, the inversion command and
66
+ * `es.against-timeline` — reads this one declaration and never a framework
67
+ * name.
68
+ */
69
+ {
70
+ id: ES_ROLE.flow,
71
+ kind: 'edge',
72
+ labelKey: 'com.labre.event-storming.role.flow',
73
+ labelFallback: 'Flow',
74
+ direction: {
75
+ verbKey: 'com.labre.event-storming.role.flow.verb',
76
+ verbFallback: 'leads to',
77
+ gestureHintKey: 'com.labre.event-storming.role.flow.gesture',
78
+ gestureHintFallback: 'Drag from what happens first to what follows.',
79
+ },
80
+ },
81
+ ];
82
+ // Null prototype: this is a lookup table keyed by ids that may one day come
83
+ // from host-supplied packs, so `defs['toString']` must not resolve.
84
+ export const EVENT_STORMING_ROLES = Object.assign(Object.create(null), Object.fromEntries(DEFS.map(def => [def.id, def])));
@@ -0,0 +1,20 @@
1
+ import type { EndpointTriplet, ValidationRule } from '@formicoidea/labre-core/blocks/surface';
2
+ /**
3
+ * The canonical Event Storming sentence, as a matrix.
4
+ *
5
+ * Read aloud, which is how it was checked: an actor issues a command; a command
6
+ * lands on an aggregate, or on an external system; an aggregate raises a domain
7
+ * event, and so does an external system; a domain event triggers a policy, and
8
+ * a domain event feeds a read model; a policy issues a command; a read model
9
+ * informs an actor.
10
+ *
11
+ * Nine sentences and no more. Every one of them is in Brandolini's own picture
12
+ * of the "Big Picture → Process modelling" grammar, and nothing has been added
13
+ * because it seemed plausible: a matrix that sanctions a sentence the notation
14
+ * does not say is worse than no matrix, because the sentence it fails to catch
15
+ * is the one somebody will ship.
16
+ *
17
+ * Exported so a test asserts THIS table rather than a copy of it.
18
+ */
19
+ export declare const ES_FLOW_MATRIX: readonly EndpointTriplet[];
20
+ export declare const EVENT_STORMING_RULES: readonly ValidationRule[];