mellos-mapping 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Layer 0 — pure operations on a MellosMap.
3
+ *
4
+ * Every operation follows validate -> prepare -> commit: all refusals happen
5
+ * before any new value is built, and the commit expression can no longer
6
+ * fail. Inputs are never mutated; the result always carries a fresh map.
7
+ *
8
+ * These functions enforce the structural invariants I1-I9 documented in
9
+ * types.ts and nothing else. In particular there are no workflow rules here:
10
+ * any status may be set at any time, in any order. Discipline lives with the
11
+ * caller; this layer only keeps the map structurally true.
12
+ */
13
+ import { type GroupId, type LaneId, type LayerId, type MapError, type MapKind, type MellosMap, type NodeId, type NodeKind, type NodeStatus, type Result, type SubmapRef } from './types.js';
14
+ /** Set or replace the map title. */
15
+ export declare function setTitle(map: MellosMap, title: string): MellosMap;
16
+ /** Set or replace the map kind (presentation intent — never structural). */
17
+ export declare function setKind(map: MellosMap, kind: MapKind): MellosMap;
18
+ export interface DeclareLaneInput {
19
+ readonly id: LaneId;
20
+ readonly label: string;
21
+ }
22
+ /** Add a new lane (I8). Declaration order is left-to-right render order. */
23
+ export declare function declareLane(map: MellosMap, input: DeclareLaneInput): Result<MellosMap, MapError>;
24
+ /**
25
+ * Remove a lane.
26
+ * Postcondition: former members stay on the map, merely off-lane — removing
27
+ * a column label never destroys work records (same contract as removeGroup).
28
+ */
29
+ export declare function removeLane(map: MellosMap, id: LaneId): Result<MellosMap, MapError>;
30
+ export interface DeclareLayerInput {
31
+ readonly id: LayerId;
32
+ readonly name: string;
33
+ readonly rank: number;
34
+ }
35
+ /** Add a new band. Refuses duplicate ids and duplicate ranks (I1). */
36
+ export declare function declareLayer(map: MellosMap, input: DeclareLayerInput): Result<MellosMap, MapError>;
37
+ export interface DeclareGroupInput {
38
+ readonly id: GroupId;
39
+ readonly label: string;
40
+ readonly layer: LayerId;
41
+ }
42
+ /** Add a new group to an existing band (I6). */
43
+ export declare function declareGroup(map: MellosMap, input: DeclareGroupInput): Result<MellosMap, MapError>;
44
+ /** Rename a group. */
45
+ export declare function updateGroup(map: MellosMap, id: GroupId, label: string): Result<MellosMap, MapError>;
46
+ /**
47
+ * Remove a group.
48
+ * Postcondition: former members stay on the map, merely ungrouped — removing
49
+ * a cluster label never destroys work records.
50
+ */
51
+ export declare function removeGroup(map: MellosMap, id: GroupId): Result<MellosMap, MapError>;
52
+ /**
53
+ * Derived, never stored: a group's aggregate status. Any regressed member
54
+ * cracks the group; else any spinner spins it; else all-done (non-empty)
55
+ * completes it; anything else is planned.
56
+ */
57
+ export declare function groupStatus(map: MellosMap, id: GroupId): NodeStatus;
58
+ /** Derived, never stored: the whole map's aggregate status (same rules as groupStatus). */
59
+ export declare function mapStatus(map: MellosMap): NodeStatus;
60
+ export interface DeclareNodeInput {
61
+ readonly id: NodeId;
62
+ readonly label: string;
63
+ readonly layer: LayerId;
64
+ readonly status?: NodeStatus;
65
+ readonly detail?: string;
66
+ readonly group?: GroupId;
67
+ readonly kind?: NodeKind;
68
+ readonly lane?: LaneId;
69
+ readonly submap?: SubmapRef;
70
+ }
71
+ /**
72
+ * Add a new node to an existing band (I2, I3), optionally joining a same-band
73
+ * group (I7) and/or an existing lane (I9).
74
+ */
75
+ export declare function declareNode(map: MellosMap, input: DeclareNodeInput): Result<MellosMap, MapError>;
76
+ /**
77
+ * Add the dependency edge `from USES to`, optionally labeled with what flows
78
+ * along it. Refuses self-edges, duplicates and any edge that does not point
79
+ * strictly downward (I4).
80
+ */
81
+ export declare function linkNodes(map: MellosMap, from: NodeId, to: NodeId, label?: string): Result<MellosMap, MapError>;
82
+ export interface UpdateNodeInput {
83
+ readonly id: NodeId;
84
+ readonly status?: NodeStatus;
85
+ readonly label?: string;
86
+ readonly evidence?: string;
87
+ readonly detail?: string;
88
+ /** A GroupId joins that group (I7 validated); null leaves the current group. */
89
+ readonly group?: GroupId | null;
90
+ /** A NodeKind sets the presentation kind; null clears it. */
91
+ readonly kind?: NodeKind | null;
92
+ /** A LaneId joins that lane (I9 validated); null leaves the current lane. */
93
+ readonly lane?: LaneId | null;
94
+ /** A SubmapRef links a child map page; null unlinks it. */
95
+ readonly submap?: SubmapRef | null;
96
+ }
97
+ /**
98
+ * Update a node's status, label, evidence, design detail, group membership,
99
+ * kind and/or lane. Absent fields are left untouched. No transition rules:
100
+ * the ledger records whatever the caller reports, whenever they report it.
101
+ */
102
+ export declare function updateNode(map: MellosMap, input: UpdateNodeInput): Result<MellosMap, MapError>;
103
+ /**
104
+ * Remove a node.
105
+ * Postcondition (explicit part of this contract): every edge touching the
106
+ * node is removed with it — a map never holds edges to missing nodes.
107
+ */
108
+ export declare function removeNode(map: MellosMap, id: NodeId): Result<MellosMap, MapError>;
109
+ /** Remove one dependency edge. */
110
+ export declare function removeEdge(map: MellosMap, from: NodeId, to: NodeId): Result<MellosMap, MapError>;
111
+ /** Remove a band. Only empty bands may go — neither a node (I2) nor a group (I6) may be orphaned. */
112
+ export declare function removeLayer(map: MellosMap, id: LayerId): Result<MellosMap, MapError>;
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Layer 0 — pure operations on a MellosMap.
3
+ *
4
+ * Every operation follows validate -> prepare -> commit: all refusals happen
5
+ * before any new value is built, and the commit expression can no longer
6
+ * fail. Inputs are never mutated; the result always carries a fresh map.
7
+ *
8
+ * These functions enforce the structural invariants I1-I9 documented in
9
+ * types.ts and nothing else. In particular there are no workflow rules here:
10
+ * any status may be set at any time, in any order. Discipline lives with the
11
+ * caller; this layer only keeps the map structurally true.
12
+ */
13
+ import { err, ok, } from './types.js';
14
+ function findLayer(map, id) {
15
+ return map.layers.find((l) => l.id === id);
16
+ }
17
+ function findNode(map, id) {
18
+ return map.nodes.find((n) => n.id === id);
19
+ }
20
+ function findGroup(map, id) {
21
+ return map.groups.find((g) => g.id === id);
22
+ }
23
+ /** Validate that `node` may join `group` (I7): the group exists on the node's own band. */
24
+ function checkMembership(map, node, nodeLayer, group) {
25
+ const g = findGroup(map, group);
26
+ if (!g)
27
+ return { kind: 'unknown-group', id: group };
28
+ if (g.layer !== nodeLayer)
29
+ return { kind: 'group-layer-mismatch', node, nodeLayer, group, groupLayer: g.layer };
30
+ return undefined;
31
+ }
32
+ function hasEdge(map, from, to) {
33
+ return map.edges.some((e) => e.from === from && e.to === to);
34
+ }
35
+ /** Set or replace the map title. */
36
+ export function setTitle(map, title) {
37
+ return { ...map, title };
38
+ }
39
+ /** Set or replace the map kind (presentation intent — never structural). */
40
+ export function setKind(map, kind) {
41
+ return { ...map, kind };
42
+ }
43
+ function findLane(map, id) {
44
+ return map.lanes.find((l) => l.id === id);
45
+ }
46
+ /** Add a new lane (I8). Declaration order is left-to-right render order. */
47
+ export function declareLane(map, input) {
48
+ if (findLane(map, input.id))
49
+ return err({ kind: 'duplicate-lane', id: input.id });
50
+ return ok({ ...map, lanes: [...map.lanes, { id: input.id, label: input.label }] });
51
+ }
52
+ /**
53
+ * Remove a lane.
54
+ * Postcondition: former members stay on the map, merely off-lane — removing
55
+ * a column label never destroys work records (same contract as removeGroup).
56
+ */
57
+ export function removeLane(map, id) {
58
+ if (!findLane(map, id))
59
+ return err({ kind: 'unknown-lane', id });
60
+ return ok({
61
+ ...map,
62
+ lanes: map.lanes.filter((l) => l.id !== id),
63
+ nodes: map.nodes.map((n) => {
64
+ if (n.lane !== id)
65
+ return n;
66
+ const { lane: _dropped, ...rest } = n;
67
+ return rest;
68
+ }),
69
+ });
70
+ }
71
+ /** Add a new band. Refuses duplicate ids and duplicate ranks (I1). */
72
+ export function declareLayer(map, input) {
73
+ if (findLayer(map, input.id))
74
+ return err({ kind: 'duplicate-layer', id: input.id });
75
+ const rankHolder = map.layers.find((l) => l.rank === input.rank);
76
+ if (rankHolder)
77
+ return err({ kind: 'duplicate-rank', rank: input.rank, existing: rankHolder.id });
78
+ return ok({ ...map, layers: [...map.layers, { id: input.id, name: input.name, rank: input.rank }] });
79
+ }
80
+ /** Add a new group to an existing band (I6). */
81
+ export function declareGroup(map, input) {
82
+ if (findGroup(map, input.id))
83
+ return err({ kind: 'duplicate-group', id: input.id });
84
+ if (!findLayer(map, input.layer))
85
+ return err({ kind: 'unknown-layer', id: input.layer });
86
+ return ok({ ...map, groups: [...map.groups, { id: input.id, label: input.label, layer: input.layer }] });
87
+ }
88
+ /** Rename a group. */
89
+ export function updateGroup(map, id, label) {
90
+ if (!findGroup(map, id))
91
+ return err({ kind: 'unknown-group', id });
92
+ return ok({ ...map, groups: map.groups.map((g) => (g.id === id ? { ...g, label } : g)) });
93
+ }
94
+ /**
95
+ * Remove a group.
96
+ * Postcondition: former members stay on the map, merely ungrouped — removing
97
+ * a cluster label never destroys work records.
98
+ */
99
+ export function removeGroup(map, id) {
100
+ if (!findGroup(map, id))
101
+ return err({ kind: 'unknown-group', id });
102
+ return ok({
103
+ ...map,
104
+ groups: map.groups.filter((g) => g.id !== id),
105
+ nodes: map.nodes.map((n) => {
106
+ if (n.group !== id)
107
+ return n;
108
+ const { group: _dropped, ...rest } = n;
109
+ return rest;
110
+ }),
111
+ });
112
+ }
113
+ /** Aggregate status over a set of nodes: regression trumps, then activity, then completion. */
114
+ function aggregateStatus(nodes) {
115
+ if (nodes.some((n) => n.status === 'regressed'))
116
+ return 'regressed';
117
+ if (nodes.some((n) => n.status === 'in-progress'))
118
+ return 'in-progress';
119
+ if (nodes.length > 0 && nodes.every((n) => n.status === 'done'))
120
+ return 'done';
121
+ return 'planned';
122
+ }
123
+ /**
124
+ * Derived, never stored: a group's aggregate status. Any regressed member
125
+ * cracks the group; else any spinner spins it; else all-done (non-empty)
126
+ * completes it; anything else is planned.
127
+ */
128
+ export function groupStatus(map, id) {
129
+ return aggregateStatus(map.nodes.filter((n) => n.group === id));
130
+ }
131
+ /** Derived, never stored: the whole map's aggregate status (same rules as groupStatus). */
132
+ export function mapStatus(map) {
133
+ return aggregateStatus(map.nodes);
134
+ }
135
+ /**
136
+ * Add a new node to an existing band (I2, I3), optionally joining a same-band
137
+ * group (I7) and/or an existing lane (I9).
138
+ */
139
+ export function declareNode(map, input) {
140
+ if (findNode(map, input.id))
141
+ return err({ kind: 'duplicate-node', id: input.id });
142
+ if (!findLayer(map, input.layer))
143
+ return err({ kind: 'unknown-layer', id: input.layer });
144
+ if (input.group !== undefined) {
145
+ const bad = checkMembership(map, input.id, input.layer, input.group);
146
+ if (bad)
147
+ return err(bad);
148
+ }
149
+ if (input.lane !== undefined && !findLane(map, input.lane))
150
+ return err({ kind: 'unknown-lane', id: input.lane });
151
+ const node = {
152
+ id: input.id,
153
+ label: input.label,
154
+ layer: input.layer,
155
+ status: input.status ?? 'planned',
156
+ ...(input.detail !== undefined ? { detail: input.detail } : {}),
157
+ ...(input.group !== undefined ? { group: input.group } : {}),
158
+ ...(input.kind !== undefined ? { kind: input.kind } : {}),
159
+ ...(input.lane !== undefined ? { lane: input.lane } : {}),
160
+ ...(input.submap !== undefined ? { submap: input.submap } : {}),
161
+ };
162
+ return ok({ ...map, nodes: [...map.nodes, node] });
163
+ }
164
+ /**
165
+ * Add the dependency edge `from USES to`, optionally labeled with what flows
166
+ * along it. Refuses self-edges, duplicates and any edge that does not point
167
+ * strictly downward (I4).
168
+ */
169
+ export function linkNodes(map, from, to, label) {
170
+ if (from === to)
171
+ return err({ kind: 'self-edge', id: from });
172
+ const fromNode = findNode(map, from);
173
+ if (!fromNode)
174
+ return err({ kind: 'unknown-node', id: from });
175
+ const toNode = findNode(map, to);
176
+ if (!toNode)
177
+ return err({ kind: 'unknown-node', id: to });
178
+ if (hasEdge(map, from, to))
179
+ return err({ kind: 'duplicate-edge', from, to });
180
+ // Layers are guaranteed to exist for stored nodes (I2), so the lookups cannot miss.
181
+ const fromRank = findLayer(map, fromNode.layer).rank;
182
+ const toRank = findLayer(map, toNode.layer).rank;
183
+ if (fromRank <= toRank)
184
+ return err({ kind: 'edge-not-downward', from, fromRank, to, toRank });
185
+ return ok({ ...map, edges: [...map.edges, { from, to, ...(label !== undefined ? { label } : {}) }] });
186
+ }
187
+ /**
188
+ * Update a node's status, label, evidence, design detail, group membership,
189
+ * kind and/or lane. Absent fields are left untouched. No transition rules:
190
+ * the ledger records whatever the caller reports, whenever they report it.
191
+ */
192
+ export function updateNode(map, input) {
193
+ const node = findNode(map, input.id);
194
+ if (!node)
195
+ return err({ kind: 'unknown-node', id: input.id });
196
+ if (input.group !== undefined && input.group !== null) {
197
+ const bad = checkMembership(map, node.id, node.layer, input.group);
198
+ if (bad)
199
+ return err(bad);
200
+ }
201
+ if (input.lane !== undefined && input.lane !== null && !findLane(map, input.lane)) {
202
+ return err({ kind: 'unknown-lane', id: input.lane });
203
+ }
204
+ const { group: currentGroup, kind: currentKind, lane: currentLane, submap: currentSubmap, ...bare } = node;
205
+ const nextGroup = input.group === undefined ? currentGroup : input.group === null ? undefined : input.group;
206
+ const nextKind = input.kind === undefined ? currentKind : input.kind === null ? undefined : input.kind;
207
+ const nextLane = input.lane === undefined ? currentLane : input.lane === null ? undefined : input.lane;
208
+ const nextSubmap = input.submap === undefined ? currentSubmap : input.submap === null ? undefined : input.submap;
209
+ const updated = {
210
+ ...bare,
211
+ ...(nextGroup !== undefined ? { group: nextGroup } : {}),
212
+ ...(nextKind !== undefined ? { kind: nextKind } : {}),
213
+ ...(nextLane !== undefined ? { lane: nextLane } : {}),
214
+ ...(nextSubmap !== undefined ? { submap: nextSubmap } : {}),
215
+ ...(input.status !== undefined ? { status: input.status } : {}),
216
+ ...(input.label !== undefined ? { label: input.label } : {}),
217
+ ...(input.evidence !== undefined ? { evidence: input.evidence } : {}),
218
+ ...(input.detail !== undefined ? { detail: input.detail } : {}),
219
+ };
220
+ return ok({ ...map, nodes: map.nodes.map((n) => (n.id === input.id ? updated : n)) });
221
+ }
222
+ /**
223
+ * Remove a node.
224
+ * Postcondition (explicit part of this contract): every edge touching the
225
+ * node is removed with it — a map never holds edges to missing nodes.
226
+ */
227
+ export function removeNode(map, id) {
228
+ if (!findNode(map, id))
229
+ return err({ kind: 'unknown-node', id });
230
+ return ok({
231
+ ...map,
232
+ nodes: map.nodes.filter((n) => n.id !== id),
233
+ edges: map.edges.filter((e) => e.from !== id && e.to !== id),
234
+ });
235
+ }
236
+ /** Remove one dependency edge. */
237
+ export function removeEdge(map, from, to) {
238
+ if (!hasEdge(map, from, to))
239
+ return err({ kind: 'unknown-edge', from, to });
240
+ return ok({ ...map, edges: map.edges.filter((e) => !(e.from === from && e.to === to)) });
241
+ }
242
+ /** Remove a band. Only empty bands may go — neither a node (I2) nor a group (I6) may be orphaned. */
243
+ export function removeLayer(map, id) {
244
+ if (!findLayer(map, id))
245
+ return err({ kind: 'unknown-layer', id });
246
+ const occupant = map.nodes.find((n) => n.layer === id);
247
+ if (occupant)
248
+ return err({ kind: 'layer-not-empty', id, occupant: occupant.id });
249
+ const groupOccupant = map.groups.find((g) => g.layer === id);
250
+ if (groupOccupant)
251
+ return err({ kind: 'layer-holds-group', id, occupant: groupOccupant.id });
252
+ return ok({ ...map, layers: map.layers.filter((l) => l.id !== id) });
253
+ }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Layer 0 — domain model of a Mellos Map.
3
+ *
4
+ * A Mellos Map is a layered dependency map of a system under construction:
5
+ * horizontal layer bands ordered by rank (rank 0 = bottom = most primitive),
6
+ * nodes living inside exactly one band, and dependency edges that may only
7
+ * point STRICTLY DOWNWARD across bands.
8
+ *
9
+ * Structural invariants owned by this layer (and only these — the map is a
10
+ * ledger, not a judge; it records work honestly and never polices workflow):
11
+ * I1. Layer ids are unique; layer ranks are unique (bands are totally ordered).
12
+ * I2. Every node belongs to exactly one existing layer.
13
+ * I3. Node ids are unique.
14
+ * I4. An edge `from -> to` means "from USES to" and requires
15
+ * rank(layer(from)) > rank(layer(to)).
16
+ * Corollary: the graph is acyclic by construction — every edge strictly
17
+ * decreases rank, so no cycle detection is ever needed.
18
+ * Same-layer edges are rejected on purpose: if A needs a sibling B,
19
+ * either B is really a lower-layer concept or A and B are one node.
20
+ * I5. Node status is one of the closed vocabulary in NODE_STATUSES.
21
+ * I6. Group ids are unique; every group lives in an existing layer.
22
+ * I7. A node's group, when set, exists and lives in the node's own layer —
23
+ * a group is band-local cohesion (a labeled subsystem the far zoom can
24
+ * render); structure ACROSS bands is what layers and edges express.
25
+ * I8. Lane ids are unique. A lane is a named vertical column CROSSING all
26
+ * bands (a sequence participant, a swim lane); lane declaration order
27
+ * is left-to-right render order.
28
+ * I9. A node's lane, when set, exists.
29
+ *
30
+ * The map kind (dev | architecture | dataflow | behavior-tree | sequence) is
31
+ * presentation intent, not structure: every kind shares the same invariants,
32
+ * and the renderer alone decides what the kind changes (legend, neutral
33
+ * status skins, lane emphasis).
34
+ *
35
+ * Everything here is immutable data plus pure types. No I/O, no clock, no
36
+ * process state.
37
+ */
38
+ /** Result type — expected failures are values, never exceptions. */
39
+ export type Result<T, E> = {
40
+ readonly ok: true;
41
+ readonly value: T;
42
+ } | {
43
+ readonly ok: false;
44
+ readonly error: E;
45
+ };
46
+ export declare const ok: <T>(value: T) => {
47
+ ok: true;
48
+ value: T;
49
+ };
50
+ export declare const err: <E>(error: E) => {
51
+ ok: false;
52
+ error: E;
53
+ };
54
+ /** Ids are branded slugs, never raw strings, so a NodeId cannot leak into a LayerId slot. */
55
+ export type NodeId = string & {
56
+ readonly __brand: 'NodeId';
57
+ };
58
+ export type LayerId = string & {
59
+ readonly __brand: 'LayerId';
60
+ };
61
+ export type GroupId = string & {
62
+ readonly __brand: 'GroupId';
63
+ };
64
+ export type LaneId = string & {
65
+ readonly __brand: 'LaneId';
66
+ };
67
+ /** Open per-node vocabulary (selector, action, db …); known kinds get a glyph in the renderer. */
68
+ export type NodeKind = string & {
69
+ readonly __brand: 'NodeKind';
70
+ };
71
+ /**
72
+ * Wiki-style link from a node to a child map's page. No existence invariant
73
+ * on purpose: declaring the reference before the page is legal — the pane
74
+ * simply has nowhere to dive until the page appears.
75
+ */
76
+ export type SubmapRef = string & {
77
+ readonly __brand: 'SubmapRef';
78
+ };
79
+ /** The shared slug grammar for every id in the system (nodes, layers, groups, lanes, kinds, store pages). */
80
+ export declare const ID_RULE: RegExp;
81
+ export declare const ID_RULE_TEXT = "lowercase letters, digits and dashes, starting with a letter or digit, 1-64 chars";
82
+ export type InvalidId = {
83
+ readonly kind: 'invalid-id';
84
+ readonly raw: string;
85
+ readonly rule: string;
86
+ };
87
+ export declare function makeNodeId(raw: string): Result<NodeId, InvalidId>;
88
+ export declare function makeLayerId(raw: string): Result<LayerId, InvalidId>;
89
+ export declare function makeGroupId(raw: string): Result<GroupId, InvalidId>;
90
+ export declare function makeLaneId(raw: string): Result<LaneId, InvalidId>;
91
+ export declare function makeNodeKind(raw: string): Result<NodeKind, InvalidId>;
92
+ export declare function makeSubmapRef(raw: string): Result<SubmapRef, InvalidId>;
93
+ /**
94
+ * Closed vocabulary of map kinds — the diagram's presentation intent.
95
+ * 'dev' (the default) is the progress ledger; the rest are documentation
96
+ * diagrams rendered with neutral skins. Structure is identical for all.
97
+ */
98
+ export declare const MAP_KINDS: readonly ["dev", "architecture", "dataflow", "behavior-tree", "sequence"];
99
+ export type MapKind = (typeof MAP_KINDS)[number];
100
+ export declare function makeMapKind(raw: string): Result<MapKind, {
101
+ kind: 'invalid-map-kind';
102
+ raw: string;
103
+ }>;
104
+ /** Closed status vocabulary. Transitions are NOT policed — see module header. */
105
+ export declare const NODE_STATUSES: readonly ["planned", "in-progress", "done", "regressed"];
106
+ export type NodeStatus = (typeof NODE_STATUSES)[number];
107
+ export declare function makeNodeStatus(raw: string): Result<NodeStatus, {
108
+ kind: 'invalid-status';
109
+ raw: string;
110
+ }>;
111
+ /** A horizontal band. rank 0 is the bottom (most primitive) band. */
112
+ export interface MapLayer {
113
+ readonly id: LayerId;
114
+ readonly name: string;
115
+ readonly rank: number;
116
+ }
117
+ /**
118
+ * A labeled cluster of same-band nodes — a subsystem. The far zoom renders
119
+ * groups instead of members, so the overview keeps meaningful names. A
120
+ * group's status is always DERIVED from its members (see groupStatus),
121
+ * never stored.
122
+ */
123
+ export interface MapGroup {
124
+ readonly id: GroupId;
125
+ readonly label: string;
126
+ readonly layer: LayerId;
127
+ }
128
+ /**
129
+ * A named vertical column crossing all bands (I8) — a sequence participant
130
+ * or an architecture swim lane. Declaration order is left-to-right.
131
+ */
132
+ export interface MapLane {
133
+ readonly id: LaneId;
134
+ readonly label: string;
135
+ }
136
+ /** A unit of work living in exactly one band. */
137
+ export interface MapNode {
138
+ readonly id: NodeId;
139
+ readonly label: string;
140
+ readonly layer: LayerId;
141
+ readonly status: NodeStatus;
142
+ /** Verification evidence for `done`, or the observed breakage for `regressed`. */
143
+ readonly evidence?: string;
144
+ /** Design notes: responsibility, contract, key decisions. Free text. */
145
+ readonly detail?: string;
146
+ /** Membership in a same-band group (I7), for the aggregated far zoom. */
147
+ readonly group?: GroupId;
148
+ /** Per-node kind (selector, action, db …); known kinds render as a glyph prefix. */
149
+ readonly kind?: NodeKind;
150
+ /** Column membership (I9), for laned kinds such as sequence. */
151
+ readonly lane?: LaneId;
152
+ /** Child map page: the pane badges the node ⊞ and double-click dives in. */
153
+ readonly submap?: SubmapRef;
154
+ }
155
+ /** `from` USES `to`. Must point strictly downward (invariant I4). */
156
+ export interface DepEdge {
157
+ readonly from: NodeId;
158
+ readonly to: NodeId;
159
+ /** What flows along the edge: a protocol, a message, a data name. */
160
+ readonly label?: string;
161
+ }
162
+ /** The whole map. A plain immutable value — operations return new maps. */
163
+ export interface MellosMap {
164
+ readonly title?: string;
165
+ /** Presentation intent; absent means 'dev' (the progress ledger). */
166
+ readonly kind?: MapKind;
167
+ readonly layers: readonly MapLayer[];
168
+ readonly groups: readonly MapGroup[];
169
+ readonly lanes: readonly MapLane[];
170
+ readonly nodes: readonly MapNode[];
171
+ readonly edges: readonly DepEdge[];
172
+ }
173
+ export declare const EMPTY_MAP: MellosMap;
174
+ /** Every way an operation can be refused, as data. */
175
+ export type MapError = InvalidId | {
176
+ readonly kind: 'invalid-status';
177
+ readonly raw: string;
178
+ } | {
179
+ readonly kind: 'duplicate-layer';
180
+ readonly id: LayerId;
181
+ } | {
182
+ readonly kind: 'duplicate-rank';
183
+ readonly rank: number;
184
+ readonly existing: LayerId;
185
+ } | {
186
+ readonly kind: 'duplicate-node';
187
+ readonly id: NodeId;
188
+ } | {
189
+ readonly kind: 'unknown-layer';
190
+ readonly id: LayerId;
191
+ } | {
192
+ readonly kind: 'unknown-node';
193
+ readonly id: NodeId;
194
+ } | {
195
+ readonly kind: 'duplicate-edge';
196
+ readonly from: NodeId;
197
+ readonly to: NodeId;
198
+ } | {
199
+ readonly kind: 'unknown-edge';
200
+ readonly from: NodeId;
201
+ readonly to: NodeId;
202
+ } | {
203
+ readonly kind: 'self-edge';
204
+ readonly id: NodeId;
205
+ } | {
206
+ readonly kind: 'duplicate-group';
207
+ readonly id: GroupId;
208
+ } | {
209
+ readonly kind: 'unknown-group';
210
+ readonly id: GroupId;
211
+ } | {
212
+ readonly kind: 'invalid-map-kind';
213
+ readonly raw: string;
214
+ } | {
215
+ readonly kind: 'duplicate-lane';
216
+ readonly id: LaneId;
217
+ } | {
218
+ readonly kind: 'unknown-lane';
219
+ readonly id: LaneId;
220
+ } | {
221
+ readonly kind: 'group-layer-mismatch';
222
+ readonly node: NodeId;
223
+ readonly nodeLayer: LayerId;
224
+ readonly group: GroupId;
225
+ readonly groupLayer: LayerId;
226
+ } | {
227
+ readonly kind: 'layer-not-empty';
228
+ readonly id: LayerId;
229
+ readonly occupant: NodeId;
230
+ } | {
231
+ readonly kind: 'layer-holds-group';
232
+ readonly id: LayerId;
233
+ readonly occupant: GroupId;
234
+ } | {
235
+ readonly kind: 'edge-not-downward';
236
+ readonly from: NodeId;
237
+ readonly fromRank: number;
238
+ readonly to: NodeId;
239
+ readonly toRank: number;
240
+ };
241
+ /** Human-readable rendering of a MapError, for tool results and logs. */
242
+ export declare function describeMapError(e: MapError): string;