@formicoidea/labre-framework-ddd-context-map 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/actions.d.ts +38 -0
- package/dist/actions.js +84 -0
- package/dist/background.d.ts +19 -0
- package/dist/background.js +51 -0
- package/dist/commands-manifest.d.ts +18 -0
- package/dist/commands-manifest.js +114 -0
- package/dist/commands.js +37 -61
- package/dist/descriptor.d.ts +7 -2
- package/dist/descriptor.js +5 -2
- package/dist/element-renderer.d.ts +17 -0
- package/dist/element-renderer.js +15 -0
- package/dist/element-view.d.ts +17 -0
- package/dist/element-view.js +18 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/legend.d.ts +20 -0
- package/dist/legend.js +56 -0
- package/dist/nudges.d.ts +19 -0
- package/dist/nudges.js +55 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +73 -0
- package/dist/roles.d.ts +65 -0
- package/dist/roles.js +64 -0
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +264 -0
- package/dist/toolbar/board-config.d.ts +30 -0
- package/dist/toolbar/board-config.js +87 -0
- package/dist/toolbar/context-map-menu.d.ts +1 -1
- package/dist/toolbar/context-map-menu.js +1 -1
- package/dist/toolbar/senior-button.d.ts +1 -0
- package/dist/toolbar/senior-button.js +1 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +9 -9
- package/dist/translations.js +22 -11
- package/dist/view.d.ts +17 -1
- package/dist/view.js +60 -2
- package/package.json +7 -3
package/dist/legend.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CM_BUBBLE, CM_RELATIONSHIPS, LABEL_COLOR, roleLabel, } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import { CM_PATTERN_ROLE, CONTEXT_MAP_ROLE, CONTEXT_MAP_ROLES } from './roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* What the Context Map board's automatic legend can say — a TABLE, and nothing
|
|
5
|
+
* else: the scan, the placement and the box are `createAutoLegend`'s job.
|
|
6
|
+
*
|
|
7
|
+
* Every row is DERIVED from the presets the palette itself draws from
|
|
8
|
+
* ({@link CM_RELATIONSHIPS}, {@link CM_BUBBLE}) and from the role vocabulary's
|
|
9
|
+
* own labels, never restated. A tenth pattern added to `CM_RELATIONSHIPS` gets
|
|
10
|
+
* its legend row here with no edit, the same way it gets its role.
|
|
11
|
+
*
|
|
12
|
+
* ## What is missing, and why
|
|
13
|
+
*
|
|
14
|
+
* The **cloud** (the "System / Big Ball of Mud" blob) carries no role: it is
|
|
15
|
+
* created neutral by `commands.ts`, deliberately — the endpoint grammar treats a
|
|
16
|
+
* relationship drawn onto a cloud as a sketch and stays silent on it. Detection
|
|
17
|
+
* is by role and only by role, so the automatic legend cannot list the cloud and
|
|
18
|
+
* does not pretend to. The palette's static Legend entry still documents it, and
|
|
19
|
+
* the day the cloud earns a role its row lands here in one line.
|
|
20
|
+
*/
|
|
21
|
+
export const CONTEXT_MAP_AUTO_LEGEND = {
|
|
22
|
+
title: 'Legend',
|
|
23
|
+
width: 290,
|
|
24
|
+
roles: CONTEXT_MAP_ROLES,
|
|
25
|
+
sections: [
|
|
26
|
+
{
|
|
27
|
+
title: 'Boundaries',
|
|
28
|
+
entries: [
|
|
29
|
+
{
|
|
30
|
+
role: CONTEXT_MAP_ROLE.context,
|
|
31
|
+
row: {
|
|
32
|
+
swatch: 'square',
|
|
33
|
+
color: CM_BUBBLE.fill,
|
|
34
|
+
label: roleLabel(CONTEXT_MAP_ROLES, CONTEXT_MAP_ROLE.context),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: 'Relationships',
|
|
41
|
+
// One entry per PATTERN, not one for `context-map:relationship`: a legend
|
|
42
|
+
// that said "Relationship" would document nothing a reader could use. The
|
|
43
|
+
// dashed sample marks the two "no real integration" patterns, exactly as
|
|
44
|
+
// the board draws them.
|
|
45
|
+
entries: CM_RELATIONSHIPS.map(preset => ({
|
|
46
|
+
role: CM_PATTERN_ROLE[preset.kind],
|
|
47
|
+
row: {
|
|
48
|
+
swatch: 'line',
|
|
49
|
+
color: LABEL_COLOR,
|
|
50
|
+
dashed: preset.dashed,
|
|
51
|
+
label: `${preset.abbrev} — ${preset.label}`,
|
|
52
|
+
},
|
|
53
|
+
})),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
package/dist/nudges.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { QualityNudge } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
/**
|
|
3
|
+
* Context Mapping **map quality** — the checklist (WS2, level 2).
|
|
4
|
+
*
|
|
5
|
+
* `./rules.ts` is level 1: deterministic, decidable, real time. This file is
|
|
6
|
+
* everything a context map needs that no algorithm can decide. The split is the
|
|
7
|
+
* taxonomy's whole point — ticking is assuming, never verifying.
|
|
8
|
+
*
|
|
9
|
+
* Every one of the four is about whether a PATTERN WAS CHOSEN rather than
|
|
10
|
+
* defaulted to. That is the failure mode of context mapping in practice: the
|
|
11
|
+
* team draws nine links because there are nine integrations, picks the arrow
|
|
12
|
+
* that looks right, and ships a map that records the current wiring instead of
|
|
13
|
+
* the relationships. Nothing on the canvas can tell those two maps apart.
|
|
14
|
+
*
|
|
15
|
+
* Registered from the flag-gated `DddContextMapViewExtension`: a checklist is
|
|
16
|
+
* tooling. Switching the flag off takes it away and leaves the ticks written on
|
|
17
|
+
* the board, unread, until it comes back.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CONTEXT_MAP_NUDGES: readonly QualityNudge[];
|
package/dist/nudges.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Mapping **map quality** — the checklist (WS2, level 2).
|
|
3
|
+
*
|
|
4
|
+
* `./rules.ts` is level 1: deterministic, decidable, real time. This file is
|
|
5
|
+
* everything a context map needs that no algorithm can decide. The split is the
|
|
6
|
+
* taxonomy's whole point — ticking is assuming, never verifying.
|
|
7
|
+
*
|
|
8
|
+
* Every one of the four is about whether a PATTERN WAS CHOSEN rather than
|
|
9
|
+
* defaulted to. That is the failure mode of context mapping in practice: the
|
|
10
|
+
* team draws nine links because there are nine integrations, picks the arrow
|
|
11
|
+
* that looks right, and ships a map that records the current wiring instead of
|
|
12
|
+
* the relationships. Nothing on the canvas can tell those two maps apart.
|
|
13
|
+
*
|
|
14
|
+
* Registered from the flag-gated `DddContextMapViewExtension`: a checklist is
|
|
15
|
+
* tooling. Switching the flag off takes it away and leaves the ticks written on
|
|
16
|
+
* the board, unread, until it comes back.
|
|
17
|
+
*/
|
|
18
|
+
export const CONTEXT_MAP_NUDGES = [
|
|
19
|
+
{
|
|
20
|
+
id: 'context-map.q1-pattern-discussed',
|
|
21
|
+
framework: 'ddd-context-map',
|
|
22
|
+
labelKey: 'com.labre.ddd-context-map.quality.pattern-discussed',
|
|
23
|
+
fallback: 'Every relationship carries a justified, discussed pattern — not the one that was easiest to draw.',
|
|
24
|
+
order: 1,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'context-map.q2-separate-ways-documented',
|
|
28
|
+
framework: 'ddd-context-map',
|
|
29
|
+
labelKey: 'com.labre.ddd-context-map.quality.separate-ways',
|
|
30
|
+
fallback: 'Separate Ways are documented: it is written down why there is no integration.',
|
|
31
|
+
order: 2,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
/**
|
|
35
|
+
* The rule this nudge replaces. "Every downstream of a Big Ball of Mud is
|
|
36
|
+
* protected" is decidable in principle — walk the edges out of the cloud and
|
|
37
|
+
* look for an ACL — and undecidable in practice on THIS canvas: the cloud
|
|
38
|
+
* carries no role (a v1 cut), so there is nothing for a rule to walk from,
|
|
39
|
+
* and a shape somebody drew a mess with is not a claim that it IS one.
|
|
40
|
+
* Level 2 is the honest home for it until the cloud has a role.
|
|
41
|
+
*/
|
|
42
|
+
id: 'context-map.q3-bbom-protected',
|
|
43
|
+
framework: 'ddd-context-map',
|
|
44
|
+
labelKey: 'com.labre.ddd-context-map.quality.bbom-protected',
|
|
45
|
+
fallback: 'Every downstream of a Big Ball of Mud is protected by an Anticorruption Layer.',
|
|
46
|
+
order: 3,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'context-map.q4-legend',
|
|
50
|
+
framework: 'ddd-context-map',
|
|
51
|
+
labelKey: 'com.labre.ddd-context-map.quality.legend',
|
|
52
|
+
fallback: 'The map has a legend: a reader who does not know the notation can read it.',
|
|
53
|
+
order: 4,
|
|
54
|
+
},
|
|
55
|
+
];
|
package/dist/profiles.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Mapping validation profiles (WS2).
|
|
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 — two maps at two levels of requirement coexist on one canvas.
|
|
7
|
+
*
|
|
8
|
+
* Registered from the flag-gated `DddContextMapViewExtension`, beside the
|
|
9
|
+
* rules: switching the Context Map flag off takes the choice away with the rest
|
|
10
|
+
* of the tooling, and a board already set to `strict` simply stops being checked
|
|
11
|
+
* until it comes back — the id stays written, untouched.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Sketch: every rule demoted to `audit`. Findings still reach `violations$` —
|
|
15
|
+
* a host panel and a conformance report see them — and the canvas says nothing.
|
|
16
|
+
*
|
|
17
|
+
* The DEFAULT, and deliberately so (PRD principle 3). A context map is drawn in
|
|
18
|
+
* a workshop, at speed, with half the links pointing the wrong way while
|
|
19
|
+
* somebody remembers which team is upstream; a tool arguing with that hand is a
|
|
20
|
+
* tool switched off within the hour. Being the default also means it WRITES
|
|
21
|
+
* NOTHING: a board on `sketch` carries no profile key, so every map ever drawn
|
|
22
|
+
* is on it, with no migration and no backfill.
|
|
23
|
+
*/
|
|
24
|
+
const sketch = {
|
|
25
|
+
id: 'context-map.sketch',
|
|
26
|
+
framework: 'ddd-context-map',
|
|
27
|
+
labelKey: 'com.labre.ddd-context-map.profile.sketch',
|
|
28
|
+
fallback: 'Sketch',
|
|
29
|
+
isDefault: true,
|
|
30
|
+
rules: {
|
|
31
|
+
'context-map.relationship-endpoints': 'audit',
|
|
32
|
+
'context-map.acl-conformist-exclusive': 'audit',
|
|
33
|
+
'context-map.pattern-on-customer-supplier': 'audit',
|
|
34
|
+
'context-map.acl-on-customer-supplier': 'audit',
|
|
35
|
+
'context-map.context-off-board': 'audit',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Strict: the deliverable level — a map that leaves the room. Every severity is
|
|
40
|
+
* spelled out rather than left absent, so the answer is readable in one place.
|
|
41
|
+
*
|
|
42
|
+
* ## The one that does not move
|
|
43
|
+
*
|
|
44
|
+
* `acl-on-customer-supplier` stays `audit` HERE TOO, and it is the reason a
|
|
45
|
+
* profile spells everything out. It is not a softer version of a contradiction,
|
|
46
|
+
* it is a different KIND of statement: the combination is legitimate — a team
|
|
47
|
+
* negotiating its needs upstream while still translating a model that is being
|
|
48
|
+
* retired — and only the team knows which case they are in. Promoting it with
|
|
49
|
+
* the others would turn a good question into a false accusation on every mature
|
|
50
|
+
* map in the corpus.
|
|
51
|
+
*
|
|
52
|
+
* Nothing here is `blocking-overridable`: nothing in this library implements
|
|
53
|
+
* refusal, and declaring it would be data claiming an effect that does not
|
|
54
|
+
* exist.
|
|
55
|
+
*/
|
|
56
|
+
const strict = {
|
|
57
|
+
id: 'context-map.strict',
|
|
58
|
+
framework: 'ddd-context-map',
|
|
59
|
+
labelKey: 'com.labre.ddd-context-map.profile.strict',
|
|
60
|
+
fallback: 'Strict',
|
|
61
|
+
rules: {
|
|
62
|
+
'context-map.relationship-endpoints': 'warning',
|
|
63
|
+
'context-map.acl-conformist-exclusive': 'warning',
|
|
64
|
+
'context-map.pattern-on-customer-supplier': 'warning',
|
|
65
|
+
// See the header: a judgement the map cannot make at any level.
|
|
66
|
+
'context-map.acl-on-customer-supplier': 'audit',
|
|
67
|
+
'context-map.context-off-board': 'warning',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
export const CONTEXT_MAP_PROFILES = [
|
|
71
|
+
sketch,
|
|
72
|
+
strict,
|
|
73
|
+
];
|
package/dist/roles.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CM_RELATIONSHIPS } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import type { RoleDefs } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
/**
|
|
4
|
+
* Context Mapping role vocabulary (WS2).
|
|
5
|
+
*
|
|
6
|
+
* Roles are the semantic identity of a Context Map artefact — no rule will ever
|
|
7
|
+
* look at a shape type. Three families:
|
|
8
|
+
*
|
|
9
|
+
* - the **board** (`context-map:board`), the white card the map is drawn on;
|
|
10
|
+
* - the **bounded context** (`context-map:context`), the blue pill;
|
|
11
|
+
* - the **relationship** (`context-map:relationship`), the edge parent, with
|
|
12
|
+
* one child per DDD pattern.
|
|
13
|
+
*
|
|
14
|
+
* ## One role per pattern, and why
|
|
15
|
+
*
|
|
16
|
+
* The PO arbitration of 26/08/2026: a relationship is not a connector with a
|
|
17
|
+
* `pattern` property, it is nine different edges. Two reasons, both of them
|
|
18
|
+
* about what the rules can then say. A pattern-per-role means a rule is written
|
|
19
|
+
* on the pattern it is about (`acl` and `conformist` may not coexist) without
|
|
20
|
+
* the engine growing a way to read a property; and it means a combination the
|
|
21
|
+
* notation genuinely allows — C/S plus OHS plus PL on the same couple, which is
|
|
22
|
+
* DDD Crew's own example — is drawn as PARALLEL LINKS, each a statement of its
|
|
23
|
+
* own, rather than as a bag of flags nobody can point at.
|
|
24
|
+
*
|
|
25
|
+
* Hierarchy is DATA (`parent`): every pattern specialises
|
|
26
|
+
* `context-map:relationship`, so a rule written on the parent — the endpoint
|
|
27
|
+
* grammar, the self-loop ban — covers all nine for free (see `roleIsA`).
|
|
28
|
+
*
|
|
29
|
+
* ## `direction` only on the U/D patterns
|
|
30
|
+
*
|
|
31
|
+
* Tier 1 of `docs/adr/0010` says an edge's source is the subject of its verb.
|
|
32
|
+
* That only MEANS something for the five upstream/downstream patterns, where
|
|
33
|
+
* the notation itself has a direction: the source is the upstream context. The
|
|
34
|
+
* four symmetric ones (Partnership, Shared Kernel, Separate Ways, Big Ball of
|
|
35
|
+
* Mud) relate two contexts as equals — declaring a verb on them would announce a
|
|
36
|
+
* gesture ("drag from the upstream one") that means nothing, and the reveal (M2)
|
|
37
|
+
* would show a sentence the framework does not say. They therefore declare none,
|
|
38
|
+
* which is exactly what an untyped edge already did.
|
|
39
|
+
*
|
|
40
|
+
* ## Compat
|
|
41
|
+
*
|
|
42
|
+
* Nothing is backfilled. Relationships drawn before these roles existed — the
|
|
43
|
+
* old free connector + tag + U/D markers group — carry no role and are never
|
|
44
|
+
* evaluated (promesse #71): they stay drawings, in the documents they are in.
|
|
45
|
+
*/
|
|
46
|
+
/** The nine DDD Crew patterns, by the `kind` used at the creation sites. */
|
|
47
|
+
export type ContextMapPatternKind = (typeof CM_RELATIONSHIPS)[number]['kind'];
|
|
48
|
+
export type ContextMapRoleId = `context-map:${string}`;
|
|
49
|
+
export declare const CM_PATTERN_ROLE: Record<ContextMapPatternKind, ContextMapRoleId>;
|
|
50
|
+
/** The three roles that are not a pattern, plus the pattern ids. */
|
|
51
|
+
export declare const CONTEXT_MAP_ROLE: {
|
|
52
|
+
readonly partnership: `context-map:${string}`;
|
|
53
|
+
readonly sharedKernel: `context-map:${string}`;
|
|
54
|
+
readonly customerSupplier: `context-map:${string}`;
|
|
55
|
+
readonly conformist: `context-map:${string}`;
|
|
56
|
+
readonly acl: `context-map:${string}`;
|
|
57
|
+
readonly ohs: `context-map:${string}`;
|
|
58
|
+
readonly publishedLanguage: `context-map:${string}`;
|
|
59
|
+
readonly separateWays: `context-map:${string}`;
|
|
60
|
+
readonly bbom: `context-map:${string}`;
|
|
61
|
+
readonly board: "context-map:board";
|
|
62
|
+
readonly context: "context-map:context";
|
|
63
|
+
readonly relationship: "context-map:relationship";
|
|
64
|
+
};
|
|
65
|
+
export declare const CONTEXT_MAP_ROLES: RoleDefs;
|
package/dist/roles.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CM_RELATIONSHIPS } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
/**
|
|
3
|
+
* Role id per pattern kind, DERIVED from the shared preset table rather than
|
|
4
|
+
* restated: the kinds are camelCase (`sharedKernel`) and the role ids are
|
|
5
|
+
* kebab-case (`context-map:shared-kernel`), and the day a tenth pattern lands in
|
|
6
|
+
* `CM_RELATIONSHIPS` it gets its role here with no edit.
|
|
7
|
+
*/
|
|
8
|
+
const kebab = (kind) => kind.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
9
|
+
export const CM_PATTERN_ROLE = Object.fromEntries(CM_RELATIONSHIPS.map(preset => [
|
|
10
|
+
preset.kind,
|
|
11
|
+
`context-map:${kebab(preset.kind)}`,
|
|
12
|
+
]));
|
|
13
|
+
/** The three roles that are not a pattern, plus the pattern ids. */
|
|
14
|
+
export const CONTEXT_MAP_ROLE = {
|
|
15
|
+
board: 'context-map:board',
|
|
16
|
+
context: 'context-map:context',
|
|
17
|
+
relationship: 'context-map:relationship',
|
|
18
|
+
...CM_PATTERN_ROLE,
|
|
19
|
+
};
|
|
20
|
+
/** The DDD Crew wording, verbatim: `Upstream` is the source of the relation. */
|
|
21
|
+
const UPSTREAM_DIRECTION = {
|
|
22
|
+
verbKey: 'com.labre.ddd-context-map.role.relationship.verb',
|
|
23
|
+
verbFallback: 'is upstream of',
|
|
24
|
+
gestureHintKey: 'com.labre.ddd-context-map.role.relationship.gesture',
|
|
25
|
+
gestureHintFallback: 'Drag from the upstream context to the downstream one.',
|
|
26
|
+
};
|
|
27
|
+
const DEFS = [
|
|
28
|
+
// The board: a frame, not a context. It specialises nothing, so a rule
|
|
29
|
+
// written on `context-map:context` can never match the card its subjects
|
|
30
|
+
// sit on.
|
|
31
|
+
{
|
|
32
|
+
id: CONTEXT_MAP_ROLE.board,
|
|
33
|
+
kind: 'node',
|
|
34
|
+
labelKey: 'com.labre.ddd-context-map.role.board',
|
|
35
|
+
labelFallback: 'Context map',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: CONTEXT_MAP_ROLE.context,
|
|
39
|
+
kind: 'node',
|
|
40
|
+
labelKey: 'com.labre.ddd-context-map.role.context',
|
|
41
|
+
labelFallback: 'Bounded context',
|
|
42
|
+
},
|
|
43
|
+
// The edge parent. Carries no `direction` of its own: five of its nine
|
|
44
|
+
// children have one and four do not, and a verb on the parent would be
|
|
45
|
+
// inherited by the symmetric patterns that deny having one.
|
|
46
|
+
{
|
|
47
|
+
id: CONTEXT_MAP_ROLE.relationship,
|
|
48
|
+
kind: 'edge',
|
|
49
|
+
labelKey: 'com.labre.ddd-context-map.role.relationship',
|
|
50
|
+
labelFallback: 'Relationship',
|
|
51
|
+
},
|
|
52
|
+
...CM_RELATIONSHIPS.map((preset) => ({
|
|
53
|
+
id: CM_PATTERN_ROLE[preset.kind],
|
|
54
|
+
parent: CONTEXT_MAP_ROLE.relationship,
|
|
55
|
+
kind: 'edge',
|
|
56
|
+
labelKey: `com.labre.ddd-context-map.role.${kebab(preset.kind)}`,
|
|
57
|
+
labelFallback: preset.label,
|
|
58
|
+
// See the header: only the upstream/downstream patterns have a subject.
|
|
59
|
+
...(preset.upDown ? { direction: UPSTREAM_DIRECTION } : {}),
|
|
60
|
+
})),
|
|
61
|
+
];
|
|
62
|
+
// Null prototype: this is a lookup table keyed by ids that may one day come
|
|
63
|
+
// from host-supplied packs, so `defs['toString']` must not resolve.
|
|
64
|
+
export const CONTEXT_MAP_ROLES = Object.assign(Object.create(null), Object.fromEntries(DEFS.map(def => [def.id, def])));
|
package/dist/rules.d.ts
ADDED
package/dist/rules.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { CONTEXT_MAP_BACKGROUND } from './background.js';
|
|
2
|
+
import { CONTEXT_MAP_ROLE, CONTEXT_MAP_ROLES } from './roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* Context Mapping validation rules (WS2).
|
|
5
|
+
*
|
|
6
|
+
* DATA owned by the framework, versioned per rule: the engine
|
|
7
|
+
* (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
|
|
8
|
+
* concrete rule. Registered from the flag-gated `DddContextMapViewExtension`,
|
|
9
|
+
* so switching the Context Map flag off removes them with the rest of the
|
|
10
|
+
* tooling — maps already drawn keep rendering, they simply stop being checked
|
|
11
|
+
* (`docs/adr/0009`).
|
|
12
|
+
*
|
|
13
|
+
* ## What the grammar of a context map actually is
|
|
14
|
+
*
|
|
15
|
+
* Very little, and that is the point. A context map has no axes: nothing about
|
|
16
|
+
* WHERE a context sits means anything, so there is no order rule, no zone rule
|
|
17
|
+
* and no orientation rule to write. What it does have is a notation with
|
|
18
|
+
* CONTRADICTIONS in it — patterns that cannot both be true of the same couple —
|
|
19
|
+
* and those are exactly what the `relation-endpoints` family arbitrates.
|
|
20
|
+
*
|
|
21
|
+
* Four of the five rules below are therefore one family with four different
|
|
22
|
+
* questions, and every one of them stays silent on a sketch: an end that is not
|
|
23
|
+
* a bounded context (a cloud, a note, a plain rectangle) is outside the
|
|
24
|
+
* alphabet, and the whole edge goes unjudged.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* **CM1** — a relationship runs between two bounded contexts, once, and never
|
|
28
|
+
* onto itself.
|
|
29
|
+
*
|
|
30
|
+
* The base grammar, written on the PARENT edge role, which is what makes it one
|
|
31
|
+
* rule instead of nine: `roleIsA` resolves a triplet declared on
|
|
32
|
+
* `context-map:relationship` for every pattern that specialises it, so the
|
|
33
|
+
* single sanctioned sentence below — a context relates to a context — covers
|
|
34
|
+
* Partnership and Big Ball of Mud alike, and a tenth pattern inherits it on the
|
|
35
|
+
* day it is added.
|
|
36
|
+
*
|
|
37
|
+
* The two things it says:
|
|
38
|
+
*
|
|
39
|
+
* - **self-loop** — a context is not related to itself. Whatever the hand meant,
|
|
40
|
+
* the notation has no sentence for it;
|
|
41
|
+
* - **duplicate** — the same pattern drawn twice between the same two contexts,
|
|
42
|
+
* the same way round. Not two DIFFERENT patterns, which is legitimate and how
|
|
43
|
+
* C/S + OHS + PL is drawn; the same one, twice, which is one link too many on
|
|
44
|
+
* a map that is read by counting links.
|
|
45
|
+
*
|
|
46
|
+
* ## What `allowed` does here, and what it deliberately does NOT
|
|
47
|
+
*
|
|
48
|
+
* The matrix holds ONE sentence, and `context-map:context` is therefore the
|
|
49
|
+
* whole alphabet of this rule. That makes the off-matrix branch structurally
|
|
50
|
+
* unreachable — an end that is a context passes, and an end that is anything
|
|
51
|
+
* else is outside the alphabet, which is TOTAL silence for the edge (see
|
|
52
|
+
* {@link RelationEndpointsDef.allowed}).
|
|
53
|
+
*
|
|
54
|
+
* That is the intent, not an oversight. The matrix is here to declare the
|
|
55
|
+
* alphabet, and the alphabet is what makes the whole rule proportionate: a
|
|
56
|
+
* relationship drawn onto a CLOUD, onto a note, onto a rectangle somebody
|
|
57
|
+
* dropped on the board to think with, is a draft — the user is saying "there is
|
|
58
|
+
* something out there we integrate with", which on a context map is not merely
|
|
59
|
+
* tolerable but the correct notation. Judging it would be indicting the act of
|
|
60
|
+
* sketching (PRD principle 8).
|
|
61
|
+
*
|
|
62
|
+
* The branch becomes reachable the day a second NODE role lands — a role on the
|
|
63
|
+
* cloud, say — and on that day this declaration already says what to do with it
|
|
64
|
+
* without a line changing. Until then `offMatrix` carries no words of its own
|
|
65
|
+
* and the rule's own sentence stands behind it, unused.
|
|
66
|
+
*/
|
|
67
|
+
const relationshipEndpoints = {
|
|
68
|
+
id: 'context-map.relationship-endpoints',
|
|
69
|
+
framework: 'ddd-context-map',
|
|
70
|
+
family: 'relation-endpoints',
|
|
71
|
+
severity: 'warning',
|
|
72
|
+
// No `appliesTo`: the subject is a RELATION, and the role that names it is
|
|
73
|
+
// declared where the family reads it — naming one of the three indicted
|
|
74
|
+
// elements here would be data that lies.
|
|
75
|
+
roles: CONTEXT_MAP_ROLES,
|
|
76
|
+
messageKey: 'com.labre.ddd-context-map.validation.relationship-endpoints',
|
|
77
|
+
messageFallback: 'This relationship does not run between two bounded contexts.',
|
|
78
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.relationship-endpoints.suggestion',
|
|
79
|
+
suggestionFallback: 'A context map relates bounded contexts — re-point the loose end onto one, or delete the link.',
|
|
80
|
+
version: 1,
|
|
81
|
+
provenance: {
|
|
82
|
+
source: 'recommendation',
|
|
83
|
+
reference: 'Context Mapping (DDD) — the relationship grammar of the notation',
|
|
84
|
+
},
|
|
85
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
86
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
87
|
+
endpoints: {
|
|
88
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
89
|
+
allowed: [
|
|
90
|
+
{
|
|
91
|
+
source: CONTEXT_MAP_ROLE.context,
|
|
92
|
+
edge: CONTEXT_MAP_ROLE.relationship,
|
|
93
|
+
target: CONTEXT_MAP_ROLE.context,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
forbidSelfLoop: true,
|
|
97
|
+
forbidDuplicate: true,
|
|
98
|
+
selfLoop: {
|
|
99
|
+
messageKey: 'com.labre.ddd-context-map.validation.relationship-self-loop',
|
|
100
|
+
messageFallback: 'This relationship loops back onto its own context.',
|
|
101
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.relationship-self-loop.suggestion',
|
|
102
|
+
suggestionFallback: 'A bounded context integrates with OTHER contexts — drop the loose end on the context it actually relates to, or delete the link.',
|
|
103
|
+
},
|
|
104
|
+
duplicate: {
|
|
105
|
+
messageKey: 'com.labre.ddd-context-map.validation.relationship-duplicate',
|
|
106
|
+
messageFallback: 'This pattern is already drawn between these two contexts.',
|
|
107
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.relationship-duplicate.suggestion',
|
|
108
|
+
suggestionFallback: 'Delete the copy. Two DIFFERENT patterns on one couple are fine — C/S with OHS and PL is the notation working — but the same one twice says nothing new.',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* **CM2** — a context does not both conform to its upstream and protect itself
|
|
114
|
+
* from it.
|
|
115
|
+
*
|
|
116
|
+
* The textbook contradiction, and the one every context map workshop produces
|
|
117
|
+
* at least once. Conformist means "we take their model as it is"; an
|
|
118
|
+
* Anticorruption Layer means "we translate their model into ours because we
|
|
119
|
+
* refuse to take it as it is". Both are legitimate answers to the same pressure
|
|
120
|
+
* and they are opposite answers, so a couple carrying both has not decided —
|
|
121
|
+
* which is the finding.
|
|
122
|
+
*
|
|
123
|
+
* A `warning` and never a refusal: the map is a discussion aid, and the pair
|
|
124
|
+
* showing up on it is usually the moment the discussion starts. The user
|
|
125
|
+
* resolves it by deleting one, and only they know which.
|
|
126
|
+
*/
|
|
127
|
+
const aclConformistExclusive = {
|
|
128
|
+
id: 'context-map.acl-conformist-exclusive',
|
|
129
|
+
framework: 'ddd-context-map',
|
|
130
|
+
family: 'relation-endpoints',
|
|
131
|
+
severity: 'warning',
|
|
132
|
+
roles: CONTEXT_MAP_ROLES,
|
|
133
|
+
messageKey: 'com.labre.ddd-context-map.validation.acl-conformist',
|
|
134
|
+
messageFallback: 'These two contexts are related by both a Conformist and an Anticorruption Layer.',
|
|
135
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.acl-conformist.suggestion',
|
|
136
|
+
suggestionFallback: 'Conforming means taking the upstream model as it is; an ACL means refusing to. Keep the one the team actually does.',
|
|
137
|
+
version: 1,
|
|
138
|
+
provenance: {
|
|
139
|
+
source: 'recommendation',
|
|
140
|
+
reference: 'Context Mapping (DDD) — the pattern canon: an anticorruption layer and a conformist are opposite answers',
|
|
141
|
+
},
|
|
142
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
143
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
144
|
+
endpoints: {
|
|
145
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
146
|
+
// No `allowed`: CM1 already judges the sentences, and repeating the matrix
|
|
147
|
+
// here would report one mistake twice. This rule asks the pair question and
|
|
148
|
+
// nothing else.
|
|
149
|
+
exclusivePairs: [[CONTEXT_MAP_ROLE.acl, CONTEXT_MAP_ROLE.conformist]],
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* **CM3** — Customer/Supplier already says how the two teams work; Conformist
|
|
154
|
+
* and Open Host Service say something else.
|
|
155
|
+
*
|
|
156
|
+
* C/S is a RELATIONSHIP OF NEGOTIATION: downstream is a customer whose needs
|
|
157
|
+
* enter the upstream team's backlog. Conformist is its negation — downstream
|
|
158
|
+
* has no say and takes what comes. OHS is a different answer again: upstream
|
|
159
|
+
* serves all comers through a published protocol precisely so it does NOT have
|
|
160
|
+
* to negotiate per consumer. Either one drawn on top of a C/S says the couple
|
|
161
|
+
* has not settled which conversation it is having.
|
|
162
|
+
*
|
|
163
|
+
* ACL is NOT in this pair list, on purpose — see {@link aclOnCustomerSupplier},
|
|
164
|
+
* where it is an audit finding rather than a warning.
|
|
165
|
+
*/
|
|
166
|
+
const patternOnCustomerSupplier = {
|
|
167
|
+
id: 'context-map.pattern-on-customer-supplier',
|
|
168
|
+
framework: 'ddd-context-map',
|
|
169
|
+
family: 'relation-endpoints',
|
|
170
|
+
severity: 'warning',
|
|
171
|
+
roles: CONTEXT_MAP_ROLES,
|
|
172
|
+
messageKey: 'com.labre.ddd-context-map.validation.pattern-on-customer-supplier',
|
|
173
|
+
messageFallback: 'This Customer/Supplier relationship carries a second pattern that contradicts it.',
|
|
174
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.pattern-on-customer-supplier.suggestion',
|
|
175
|
+
suggestionFallback: 'Customer/Supplier means the downstream needs are negotiated into the upstream backlog. Conformist says they are not; Open Host Service says the upstream serves everyone the same way. Keep one.',
|
|
176
|
+
version: 1,
|
|
177
|
+
provenance: {
|
|
178
|
+
source: 'recommendation',
|
|
179
|
+
reference: 'Context Mapping (DDD) — Customer/Supplier is a relationship of negotiation',
|
|
180
|
+
},
|
|
181
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
182
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
183
|
+
endpoints: {
|
|
184
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
185
|
+
exclusivePairs: [
|
|
186
|
+
[CONTEXT_MAP_ROLE.customerSupplier, CONTEXT_MAP_ROLE.conformist],
|
|
187
|
+
[CONTEXT_MAP_ROLE.customerSupplier, CONTEXT_MAP_ROLE.ohs],
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* **CM4** — an Anticorruption Layer on a Customer/Supplier is worth a second
|
|
193
|
+
* look, and nothing more.
|
|
194
|
+
*
|
|
195
|
+
* Deliberately `audit`, alone among the four. The combination is not wrong: a
|
|
196
|
+
* downstream team can perfectly well negotiate its needs upstream AND still
|
|
197
|
+
* translate the upstream model at its border, and mature teams do exactly that
|
|
198
|
+
* while a legacy model is being retired. But it is also, often, the trace of a
|
|
199
|
+
* negotiation that has stopped working — the customer built a translation layer
|
|
200
|
+
* because the supplier stopped listening.
|
|
201
|
+
*
|
|
202
|
+
* A tool cannot tell those two apart, so it does not get to interrupt: the
|
|
203
|
+
* finding reaches `violations$` for a host panel and a conformance report, and
|
|
204
|
+
* the canvas says nothing. `strict` leaves it at `audit` too, which is the whole
|
|
205
|
+
* reason a profile spells every severity out — this one is a judgement the map
|
|
206
|
+
* cannot make at any level of requirement.
|
|
207
|
+
*/
|
|
208
|
+
const aclOnCustomerSupplier = {
|
|
209
|
+
id: 'context-map.acl-on-customer-supplier',
|
|
210
|
+
framework: 'ddd-context-map',
|
|
211
|
+
family: 'relation-endpoints',
|
|
212
|
+
severity: 'audit',
|
|
213
|
+
roles: CONTEXT_MAP_ROLES,
|
|
214
|
+
messageKey: 'com.labre.ddd-context-map.validation.acl-on-customer-supplier',
|
|
215
|
+
messageFallback: 'This Customer/Supplier relationship also carries an Anticorruption Layer.',
|
|
216
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.acl-on-customer-supplier.suggestion',
|
|
217
|
+
suggestionFallback: 'Legitimate while a model is being retired — worth asking whether the negotiation still works if it is not.',
|
|
218
|
+
version: 1,
|
|
219
|
+
provenance: {
|
|
220
|
+
source: 'labre-convention',
|
|
221
|
+
reference: 'Labre convention — the notation forbids nothing here; a tool cannot tell the two readings apart',
|
|
222
|
+
},
|
|
223
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
224
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
225
|
+
endpoints: {
|
|
226
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
227
|
+
exclusivePairs: [[CONTEXT_MAP_ROLE.customerSupplier, CONTEXT_MAP_ROLE.acl]],
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* **CM5** — a bounded context belongs on the map.
|
|
232
|
+
*
|
|
233
|
+
* The one geometric rule the framework has, and it is about membership rather
|
|
234
|
+
* than position: a context parked beside the card is not on the map, so nothing
|
|
235
|
+
* the map says covers it. Silent when the board carries no board element at all
|
|
236
|
+
* — a context map sketched on the bare canvas is a sketch, not an error, and so
|
|
237
|
+
* is one drawn before the board existed.
|
|
238
|
+
*/
|
|
239
|
+
const contextOffBoard = {
|
|
240
|
+
id: 'context-map.context-off-board',
|
|
241
|
+
framework: 'ddd-context-map',
|
|
242
|
+
family: 'element-in-background',
|
|
243
|
+
severity: 'warning',
|
|
244
|
+
appliesTo: CONTEXT_MAP_ROLE.context,
|
|
245
|
+
roles: CONTEXT_MAP_ROLES,
|
|
246
|
+
messageKey: 'com.labre.ddd-context-map.validation.context-off-board',
|
|
247
|
+
messageFallback: 'This bounded context sits outside the map.',
|
|
248
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.context-off-board.suggestion',
|
|
249
|
+
suggestionFallback: 'Drag it onto the board, or grow the board to take it in.',
|
|
250
|
+
version: 1,
|
|
251
|
+
provenance: {
|
|
252
|
+
source: 'labre-convention',
|
|
253
|
+
reference: 'Labre convention — membership on this canvas, not a Context Mapping rule',
|
|
254
|
+
},
|
|
255
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
256
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
257
|
+
};
|
|
258
|
+
export const CONTEXT_MAP_RULES = [
|
|
259
|
+
relationshipEndpoints,
|
|
260
|
+
aclConformistExclusive,
|
|
261
|
+
patternOnCustomerSupplier,
|
|
262
|
+
aclOnCustomerSupplier,
|
|
263
|
+
contextOffBoard,
|
|
264
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ToolbarContext } from '@formicoidea/labre-core/shared/services';
|
|
2
|
+
/**
|
|
3
|
+
* The selected board's contextual toolbar: the resize toggle, and the automatic
|
|
4
|
+
* legend of what is actually drawn on the board. Registered ALWAYS-ON
|
|
5
|
+
* (`DddContextMapRenderViewExtension`) — a stored board must stay usable with
|
|
6
|
+
* the Context Map button switched off (`docs/adr/0009`), legend included: a
|
|
7
|
+
* legend is real editable elements, so generating one is authoring a document,
|
|
8
|
+
* not tooling that a flag may take away.
|
|
9
|
+
*
|
|
10
|
+
* This is THE legend of the module. The palette used to also offer a static,
|
|
11
|
+
* full-notation one; the PO's recette (27/08/2026) removed it — two legends were
|
|
12
|
+
* two answers to one question, and the honest answer is what is actually drawn.
|
|
13
|
+
* Same single-gesture shape as Core Domain Chart.
|
|
14
|
+
*/
|
|
15
|
+
export declare const contextMapBoardToolbarConfig: {
|
|
16
|
+
readonly actions: [{
|
|
17
|
+
readonly id: "a.toggle-resize";
|
|
18
|
+
readonly tooltipWording: import("@formicoidea/labre-core/shared/services").ChromeWording;
|
|
19
|
+
readonly icon: import("lit-html").TemplateResult<1>;
|
|
20
|
+
readonly active: (ctx: ToolbarContext) => boolean;
|
|
21
|
+
readonly run: (ctx: ToolbarContext) => void;
|
|
22
|
+
}, {
|
|
23
|
+
readonly id: "b.legend";
|
|
24
|
+
readonly tooltipWording: import("@formicoidea/labre-core/shared/services").ChromeWording;
|
|
25
|
+
readonly icon: import("lit-html").TemplateResult<1>;
|
|
26
|
+
readonly run: (ctx: ToolbarContext) => void;
|
|
27
|
+
}];
|
|
28
|
+
readonly when: (ctx: ToolbarContext) => boolean;
|
|
29
|
+
};
|
|
30
|
+
export declare const contextMapBoardToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
|