@g1cloud/bpmn-modeler-next 5.0.0-alpha.5 → 5.0.0-alpha.51

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.
@@ -1,6 +1,6 @@
1
1
  import { BpmnDocumentModelerLike } from '../core/modelerLike';
2
2
  import { SymbolicOp } from './symbolicOp';
3
- import { ResolveIssue } from './resolver';
3
+ import { ApplyBpmnOpsResult, ResolveIssue } from './resolver';
4
4
  import { GeometryFinding } from './geometryAudit';
5
5
  /** op 출처 — 감사·정책 분기용. `gui` = 호스트 채팅 패널, `rest` = 외부 브리지. */
6
6
  export type BpmnOpOrigin = 'agent' | 'gui' | 'rest';
@@ -41,6 +41,8 @@ export type ApplyBatchOutcome =
41
41
  version: number;
42
42
  xml: string;
43
43
  findings: GeometryFinding[];
44
+ /** 간선 교차 계측(B-14 ⓐ) — 문서 전체, 하강 전/후. finding 이 아니다. */
45
+ stats: ApplyBpmnOpsResult['stats'];
44
46
  } | {
45
47
  status: 'conflict';
46
48
  baseVersion: number;
@@ -16,6 +16,37 @@ export interface GeometryShape {
16
16
  container: boolean;
17
17
  /** 경계 이벤트처럼 **호스트에 붙는 것이 정상**인 도형 — 겹침 판정에서 제외된다. */
18
18
  attached: boolean;
19
+ /**
20
+ * 이 도형을 **의미론적으로** 담는 컨테이너 id(참여자 또는 확장 서브프로세스) — 없으면 루트.
21
+ * 좌표가 아니라 모델에서 온다(DI 는 프로세스 소속을 말하지 않는다). `shape-outside-container`
22
+ * 판정의 근거: 노드가 자기 풀 밖에 그려져 있으면 좌표만 보는 사람은 **남의 풀 노드로 읽는다.**
23
+ */
24
+ parent?: string;
25
+ /**
26
+ * 도형의 **외부 이름 라벨** bounds(이벤트·게이트웨이의 이름은 도형 밖에 그려진다 — 있을 때만).
27
+ * `annotation-over-label` 의 입력(B-24 ⓑ): 주석 상자가 이 위에 놓이면 텍스트가 겹쳐 둘 다 읽을 수 없다.
28
+ *
29
+ * ⚠ **블랙박스 풀은 예외적으로 "움직일 수 없는" 라벨을 갖는다**(B-38) — `processRef` 없는 참여자는
30
+ * bpmn-js 가 이름을 풀 **본체 중앙에 가로로** 그리고(`isExpanded` = `!!processRef` → 렌더러 else 분기,
31
+ * `align: 'center-middle'`), 그 좌표는 DI 에 `BPMNLabel` 로 남지 않는 **렌더 산물**이다. 위치가 폭의
32
+ * 함수라 라벨을 옮겨서는 못 고치고 **풀 기하를 바꿔야** 한다. 라벨을 이동시키는 보정
33
+ * (`repairIntroducedShapeLabelLines`)은 `container` 로 이미 걸러진다.
34
+ */
35
+ label?: GeometryLabel;
36
+ /**
37
+ * 그룹의 멤버 id 목록(`bwl:members` 힌트, A-4 — 그룹일 때만). `group-overlap-unintended` 의 입력:
38
+ * 두 그룹의 바운즈가 겹칠 때 **멤버를 공유하면 의도된 겹침**(한 노드가 두 단계에 걸친다)이고,
39
+ * 공유하지 않으면 **그리다 보니 겹친 것**이다. 힌트가 없는 그룹(사람이 GUI 로 그린 것)은 판단할
40
+ * 근거가 없으므로 판정하지 않는다.
41
+ */
42
+ members?: readonly string[];
43
+ }
44
+ /** 간선 외부 라벨의 bounds — 라벨은 판정 대상 도형이 아니지만 **어느 간선의 것으로 읽히는가**는 판정한다(B-15). */
45
+ export interface GeometryLabel {
46
+ x: number;
47
+ y: number;
48
+ width: number;
49
+ height: number;
19
50
  }
20
51
  export interface GeometryEdge {
21
52
  id: string;
@@ -23,6 +54,18 @@ export interface GeometryEdge {
23
54
  waypoints: GeometryPoint[];
24
55
  /** 이 간선이 자기 끝점으로 삼는 도형 id 들 — 그 도형과의 교차는 도킹이라 관통이 아니다. */
25
56
  endpoints: readonly string[];
57
+ /**
58
+ * 방향 있는 끝점 — `'throughpass'`(B-63) 의 입력. ⚠ **`endpoints` 로 대신할 수 없다**: 두 생성
59
+ * 경로 모두 `[source, target]` 순으로 담지만 falsy 를 걸러내므로 한쪽이 없으면 남은 하나가
60
+ * `[0]` 으로 올라와 **방향이 뒤바뀐다**. 관통은 유입·유출을 가르는 술어라 방향을 틀리면
61
+ * 표본 50(유입 0 = 눌러앉음)과 표본 51(유입 2 = 지나감)의 판정이 뒤집힌다.
62
+ *
63
+ * 둘 다 있을 때만 관통 판정의 입력이 된다(없으면 침묵 — 오탐보다 침묵).
64
+ */
65
+ source?: string;
66
+ target?: string;
67
+ /** 이름이 있는 간선의 외부 라벨 bounds(있을 때만). `label-on-shared-segment` 의 입력. */
68
+ label?: GeometryLabel;
26
69
  }
27
70
  /** 판정기가 대면하는 최소 입력 — 좌표만 담는다(의미론 없음). */
28
71
  export interface GeometryScene {
@@ -47,9 +90,372 @@ export type GeometryFinding =
47
90
  kind: 'shape-overlap';
48
91
  shapes: [string, string];
49
92
  identical: boolean;
93
+ }
94
+ /**
95
+ * 도형이 자기 컨테이너(풀·확장 서브프로세스) 경계 밖으로 나가 있다. 원인은 컨테이너 크기가
96
+ * 내용물을 따라 늘지 않은 것(B-12 실측: 풀 높이가 band 수를 몰라 band 2 노드가 아래 풀 안에
97
+ * 놓였다). 작은 도형(이벤트)은 아무것과도 겹치지 않아 **관통·겹침 축은 침묵**하는데, 사람은
98
+ * 그 노드를 다른 풀의 것으로 읽는다 — 의미론이 조용히 뒤바뀌는 클래스라 따로 잡는다.
99
+ */
100
+ | {
101
+ kind: 'shape-outside-container';
102
+ shape: string;
103
+ container: string;
104
+ }
105
+ /**
106
+ * 두 간선의 축 정렬 구간이 **같은 선 위에 겹치는데 진행 방향이 반대**다 — 한 선처럼 보이는데
107
+ * 화살표가 양쪽 끝에 달려 어디서 어디로 가는지 읽을 수 없다(확정 6 네 번째 표본에서 사용자가
108
+ * 지목한 "문제가 되는 겹침" — 같은 방향으로 합류·팬아웃하는 겹침은 문제가 아니라 제외한다).
109
+ * 전형 = 되돌아가는 간선이 주 줄의 정방향 간선 위를 거꾸로 달리는 것, 같은 열을 반대로 오르내리는
110
+ * 메시지플로우 둘. `length` = 겹친 길이(px).
111
+ */
112
+ | {
113
+ kind: 'edge-overlap-opposite';
114
+ edges: [string, string];
115
+ axis: 'h' | 'v';
116
+ length: number;
117
+ }
118
+ /**
119
+ * 두 간선의 축 정렬 구간이 같은 선 위에 **같은 방향으로** 겹치는데 **종류가 다르다**(실선 시퀀스 ×
120
+ * 점선 메시지 · 점선 연관) — 일곱 번째 표본에서 사용자가 지목: "같은 화살표 방향이면 겹쳐도 좋지만
121
+ * 다른 종류(실선/점선)가 겹치면 가독성을 해친다". 같은 종류·같은 방향(합류·팬아웃)은 여전히 제외.
122
+ * 전형 = 되돌아가는 시퀀스가 목표 아랫면 중심으로 들어오는 열에 메시지플로우도 중심 도킹하는 것.
123
+ */
124
+ | {
125
+ kind: 'edge-overlap-mixed';
126
+ edges: [string, string];
127
+ axis: 'h' | 'v';
128
+ length: number;
129
+ }
130
+ /**
131
+ * 간선의 **라벨이 다른 간선과 공유하는 구간 위**에 놓여 어느 간선의 라벨인지 읽을 수 없다(확정 6
132
+ * 다섯 번째 표본에서 사용자가 지목: "겹치는 선상에 있는 분기 문구가 어느 선에 해당하는지 애매").
133
+ * 전형 = 게이트웨이 팬아웃의 3점 경로(세로 → 가로)는 첫 세로 구간을 갈래끼리 공선 공유하는데,
134
+ * bpmn-js 기본 라벨 위치(중간 구간 중점 = 3점이면 첫 구간)가 정확히 그 공유 구간이라 '승인'·
135
+ * '보완 요청' 이 한 선 위에 나란히 선다. 같은 방향 공선 중첩은 선으로서는 문제가 아니지만
136
+ * (`edge-overlap-opposite` 가 제외한 이유) **라벨 귀속** 축에서는 문제다. `other` = 그 구간을
137
+ * 함께 쓰는 간선(여럿이면 id 최소).
138
+ */
139
+ | {
140
+ kind: 'label-on-shared-segment';
141
+ edge: string;
142
+ other: string;
143
+ axis: 'h' | 'v';
144
+ }
145
+ /**
146
+ * 간선 라벨의 상자를 **선이 가로지른다** — 자기 간선(또는 다른 간선)의 축 정렬 구간이 상자 안을 지나거나,
147
+ * 컨테이너(레인·풀) 경계선이 상자를 가른다(열세 번째 표본에서 사용자가 지목: "선에 달린 글자가 선과 겹치거나
148
+ * 레인선상에 걸린다"). 전형 = 세로 2점 메시지플로우의 라벨 — bpmn-js 는 라벨 **중심**을 선 + 15 에 두므로 폭 42
149
+ * 라벨은 6px, 폭 64 는 17px 선 위에 걸친다. 가로 구간의 라벨(중심이 선 위 15, 높이 14)은 걸치지 않는다.
150
+ * `line` = 그 간선 id 또는 컨테이너 id. **`edge` = 라벨의 소유자 id** — 간선 라벨이면 간선, **도형의 외부 이름 라벨**
151
+ * (게이트웨이·이벤트 이름, B-29)이면 그 도형 id(스물네 번째 표본: 되돌아가는 간선 가로선이 게이트웨이 이름 라벨을
152
+ * 가로질러 사용자가 세 회차 연속 라벨을 올렸다 — bpmn-js 적응 배치는 도킹 면만 보고 지나가는 선은 안 본다).
153
+ */
154
+ | {
155
+ kind: 'label-over-line';
156
+ edge: string;
157
+ line: string;
158
+ }
159
+ /**
160
+ * 텍스트 주석 상자가 다른 요소의 **외부 라벨**(게이트웨이·이벤트 이름, 간선 라벨) 위에 놓였다 — 두 텍스트가
161
+ * 겹쳐 어느 쪽도 읽을 수 없다(열여덟 번째 표본: `top` 주석이 `처리 결과?` 게이트웨이의 이름 라벨을 덮었고
162
+ * 사용자는 "라벨과 코멘트가 비슷한 위치" 라며 주석을 옆으로 옮겼다). 주석 배치가 도형·간선 구간만 피하고
163
+ * 라벨은 보지 않던 공백. `label` = 그 라벨을 소유한 요소 id(도형 또는 간선).
164
+ */
165
+ | {
166
+ kind: 'annotation-over-label';
167
+ annotation: string;
168
+ label: string;
169
+ }
170
+ /**
171
+ * 두 그룹의 바운즈가 겹치는데 **멤버를 하나도 공유하지 않는다** — 사람은 그 겹침이 업무적 판단인지
172
+ * (한 활동이 두 단계에 걸친다) 그리다 보니 생긴 것인지 구분할 수 없다(G12 실측에서 사용자가 지목:
173
+ * "그룹이 겹쳐서 판단에 의한 건지 그리다 보니 겹친 건지 판단이 잘 안 되는 상태"). 멤버를 공유하는
174
+ * 겹침은 **의도로 읽히므로 제외**한다 — 겹침 자체가 결함이 아니라 *의도가 표현되지 않은 겹침*이 결함이다.
175
+ * 전형 = 단계 그룹은 x 구간인데 멤버가 여러 풀에 흩어져 x 범위가 서로 물리는 경우(A-4 멤버 bbox 의
176
+ * 구조적 한계). `groups` = 겹친 두 그룹(id 오름차순).
177
+ */
178
+ | {
179
+ kind: 'group-overlap-unintended';
180
+ groups: [string, string];
181
+ /**
182
+ * 한쪽 상자가 다른 쪽을 **완전히 감쌀 때만** 존재하는 바깥 그룹의 id(B-52). 같은 kind 안에서
183
+ * *부수 겹침*과 *삼킴*은 심각도가 다르다 — 표본 44 에서 사용자가 `Grp_return` 이 `Grp_settle`
184
+ * 을 통째로 덮은 것을 보고 **"정산까지 잡아먹고 있어서 근본적으로 고쳐야 한다"** 고 했고, 그것은
185
+ * 이 계보에서 가장 센 표현이다(다른 회차는 "인지하기 어렵다"·"이해 가능한 범위").
186
+ *
187
+ * 🔴 **id 가 아니라 `true` 를 주면 에이전트가 행동할 수 없다** — `groups` 는 id 오름차순이라
188
+ * 순서가 어느 쪽이 삼켰는지 말하지 않는다. 해소 수단(바깥 그룹의 멤버를 좁힌다)은 **대상을
189
+ * 지목해야** 실행된다.
190
+ *
191
+ * 🔴 **임계를 두지 않는다** — 이 축에서 「양」으로 가르려는 시도는 세 번 기각됐다(겹침 비율 ·
192
+ * `R_box − R_mem` gap · 남의 멤버 개수). 판별자는 양이 아니라 **포함이라는 관계**다.
193
+ */
194
+ container?: string;
195
+ }
196
+ /**
197
+ * 두 그룹이 겹치면서 멤버를 공유해 **겹침 자체는 의도로 읽히는데**(그래서 `group-overlap-unintended`
198
+ * 가 침묵한다), 그 겹친 영역 안에 **어느 그룹의 멤버도 아닌 흐름 노드**가 놓여 있다(B-60). 공유 멤버는
199
+ * *두 상자의 관계*만 설명할 뿐 **영역 안 제3의 노드가 어느 단계인지는 말하지 않는다** — 사람은 그
200
+ * 노드를 읽을 근거가 없다. 표본 47 에서 사용자가 **손대지도 않고 포기**했다("그룹겹침은 여전해서
201
+ * 수정할 품이 많이 들어갈것 같아").
202
+ *
203
+ * 🔴 **비율로 가르지 말 것** — B-52 에서 실측 기각됐다(표본 32 에서 사용자가 **72%** 겹침을 그대로
204
+ * 저장했고, 포기한 표본 44·47 은 **70%** 다). 판별자는 비율이 아니라 **영역 안에 든 무소속 노드**다:
205
+ * 수용된 72% 케이스는 영역 안 5개가 **전부 어느 그룹의 멤버**였다(무소속 0).
206
+ *
207
+ * 근거(오탐): 실무 16문서에는 멤버 공유 겹침 쌍 자체가 **0**. 사용자 저장본 36종의 침묵 쌍 24 중
208
+ * **1건**, fixture 48종의 침묵 쌍 30 중 **2건**이고 그 3건이 전부 **사용자가 포기한 회차**(표본 44·45)의
209
+ * 산출물이다. 수용된 49쌍은 **전량 무소속 0**.
210
+ *
211
+ * `groups` = 겹친 두 그룹(id 오름차순) · `shapes` = 영역 안의 무소속 흐름 노드(id 오름차순).
212
+ */
213
+ | {
214
+ kind: 'group-overlap-unreadable';
215
+ reason: 'orphan';
216
+ groups: [string, string];
217
+ shapes: string[];
218
+ }
219
+ /**
220
+ * 한 그룹 상자 안에서 **자기 멤버가 과반이 아니다**(B-62) — 상자 안 흐름 노드의 절반 이상이
221
+ * *남의 그룹 멤버이거나 무소속*이라 사람은 그 상자가 무슨 단계인지 읽을 근거를 잃는다. 표본 50
222
+ * 에서 사용자가 지목하고 포기했다("배송그룹에 에 반품이 포함되어있는게 좀 이상한것 같아서
223
+ * 확인해야 수정할 수 있을것 같아") — 실측 = `Grp_deliver` 안 18 = 자기 8 · **남의 8** · 무소속 2.
224
+ *
225
+ * 🔴 **같은 kind 안의 다른 축이다 — `reason` 으로 가른다.** `'orphan'` 은 *두 상자의 겹침 영역*에
226
+ * 갇힌 **무소속**을 세고(B-60), 이쪽은 *상자 하나 전체*에서 **비자기**를 센다. 표본 50 이 둘을
227
+ * 갈랐다: `'orphan'` 축은 그 상자에서 **무소속 2 만** 보고했는데 사람이 본 것은 **남의 멤버 8**
228
+ * 이었다(§0 ③ — 술어가 짚는 자리는 맞았고 *세는 것*이 틀렸다).
229
+ *
230
+ * 🔴 **비율로 가르지 말 것 — 네 번 기각됐다.** 「남의 멤버 개수 ≥ K」는 표본 32(사용자 **수용**)를
231
+ * 오탐하고(남의 4), 「공유 ≥ 단독」 순수 기하도 같은 케이스를 오탐한다. 판별자는 개수도 겹침
232
+ * 비율도 아니라 **자기 단계가 그 상자에서 과반인가**다 — 50% 는 고른 임계가 아니라 판독의 정의다.
233
+ *
234
+ * 근거(오탐): 실무 16문서 **0**(레거시라 `bwl:members` 가 0건 = 술어의 입력이 없다 → 멤버 미선언
235
+ * 그룹은 제외한다). 사용자 저장본 57종의 그룹 116개 중 **4건**, fixture 56종의 그룹 165개 중
236
+ * **6건**이고 **전부 사용자가 포기한 회차**(표본 44·47·49·50)의 산출물이다. 수용 회차(표본 32·48)는
237
+ * 전량 침묵.
238
+ *
239
+ * ⚠ **`'orphan'` 축의 처방이 이 축을 끈다** — 무소속을 멤버에 넣으면(B-60 의 대가 0 수단) 자기가
240
+ * 늘어 과반이 회복된다. 그러나 **남의 멤버를 자기 것으로 만드는 것은 의미를 훼손한다**(표본 50 에서
241
+ * 에이전트가 *"그림이 거짓말을 하게 된다"* 며 스스로 거부했다). 수단은 단계 경계를 다시 잡는 것이다.
242
+ *
243
+ * `group` = 그 상자 · `shapes` = 상자 안의 비자기 흐름 노드(남의 멤버 + 무소속 · id 오름차순).
244
+ */
245
+ | {
246
+ kind: 'group-overlap-unreadable';
247
+ reason: 'minority';
248
+ group: string;
249
+ shapes: string[];
250
+ }
251
+ /**
252
+ * 다른 단계의 흐름이 이 상자를 **지나간다**(B-63) — 상자 안에 든 *남의 그룹 멤버*로 시퀀스가
253
+ * 밖에서 들어와(유입 ≥1) 밖으로 나간다(유출 ≥1). 사람은 상자가 어디서 끝나는지 읽을 수 없다.
254
+ * 표본 51 에서 사용자가 지목하고 포기했다("입고예정일안내 → 입고대기종료 에 이르는 노테이션이
255
+ * 배송 그룹에 일부 속해서 **관통**하는데 속하지 않을것 같다") — 증언의 단어가 그대로 술어다.
256
+ *
257
+ * 🔴 **`'minority'` 와 상보다 — 상위집합이 아니다.** 같은 `Grp_deliver` 인데 형상이 정반대였다:
258
+ * 표본 50 은 남의 멤버 8 이 들어있지만 **유입 0**(반품 체인의 `Start_ret_cs` 가 상자 안에 있다
259
+ * = 밖에서 들어오지 않고 **눌러앉았다**) 이고, 표본 51 은 유입 2 · 유출 3 으로 **지나간다**.
260
+ * 사용자 증언의 단어도 갈렸다 — 표본 50 *"반품이 **포함**되어있는게"* vs 표본 51 *"**관통**하는데"*.
261
+ * ⇒ `'minority'` 는 **양**(들어앉은 정도)을, 이 축은 **위상**(지나감)을 잰다. 각각이 잡는 회차가
262
+ * 다르고 합쳐야 포기 6회차를 덮는다.
263
+ *
264
+ * 🔴 **무소속을 세지 말 것 — 세면 술어가 `'minority'` 의 상위집합이 되어 얼굴이 중복된다.**
265
+ * N20 실측: 「비자기(남의 멤버 + 무소속) 관통」으로 재면 `'minority'` 발화가 **전부 포함**되고
266
+ * (저장본·fixture 양쪽에서 "minority 만" = 0) 기저가 **17~20%** 로 뛴다. 무소속 게이트웨이 하나가
267
+ * 지나가는 형상(`Grp_intake` — 저장본에서 **다섯 회차 반복**)이 전량 오탐으로 들어오기 때문이다.
268
+ * 사용자가 지목한 것은 *"일부 **속해서**"* = 남의 멤버다.
269
+ *
270
+ * ⚠ **단일 노드 통과는 세지 않는다**(`foreign → foreign` 간선 ≥1 요구) — 노드 하나가 지나가는
271
+ * 것은 "경계에 걸친 것" 으로 읽히고, 여러 노드가 이어져 들어앉아야 "다른 단계가 들어와 있다" 로
272
+ * 읽힌다. 위상 조건이지 임계가 아니다(≥2 = "단일이 아니다"). 검정 성적은 이 조건 유무와 같고
273
+ * 기저만 6.5% → 4.1% 로 내려간다.
274
+ *
275
+ * 🔴 **판별자로 「양」을 재지 말 것 — 다섯 번 무너졌다**(개수 · 비율 72%수용/70%포기 · gap ·
276
+ * 순수기하 · 과반). 이 축은 위상이라 고를 숫자가 없다.
277
+ *
278
+ * 근거(오탐): 실무 16문서 **0**(레거시는 `bwl:members` 0건 = 술어의 입력이 없다). 저장본 58종의
279
+ * 그룹 123개 중 **5건**, fixture 58종의 그룹 178개 중 **10건**이고 **전부 사용자가 포기한 회차**
280
+ * (표본 44·46·47·49·51)의 산출물이다. 수용 회차는 전량 침묵 — 표본 32(`g12-v4` = 남의 4 가
281
+ * 있지만 유입 0·유출 0 인 **완결 체인**이라 귀속이 모호하지 않다 · **N66 통과**)와 표본 48.
282
+ * 놓치는 두 회차도 정합하다 — 표본 50 은 `'minority'` 가 잡고, 표본 45 는 근인이 면 과점유(B-55)다.
283
+ *
284
+ * `group` = 그 상자 · `shapes` = 상자를 지나가는 남의 멤버(id 오름차순).
285
+ */
286
+ | {
287
+ kind: 'group-overlap-unreadable';
288
+ reason: 'throughpass';
289
+ group: string;
290
+ shapes: string[];
291
+ }
292
+ /**
293
+ * **세 그룹 이상이 한 영역을 동시에 덮는다**(B-59) — 그 영역 안의 도형은 세 단계에 한꺼번에 속한
294
+ * 것처럼 보여 어느 단계인지 읽을 수 없다. 표본 46 에서 사용자가 지목했다("그룹 중첩이 많아서
295
+ * 인지하기가 어려워서야 출고/검수/배송이 중첩이라서 … 내가 의도를 에이전트한테 물어봐야하는
296
+ * 상황일것 같아") 그리고 표본 49 에서 다시 포기했다("완전히 겹치던지 안겹치던지 하는게 맞을것
297
+ * 같은데 의도 파악이 안되는 상황이야").
298
+ *
299
+ * 🔴 **`group-overlap-unreadable`(B-60)이 이 형상을 못 잡는다 — 상보적이다.** 근인은 B-32 ⓑ 의
300
+ * "멤버 교집합 ≥1 = 의도" 술어가 **인접 단계 그룹에 대해 항상 참**이라는 것이다. 연속한 단계가
301
+ * 경계 노드를 공유하는 것은 정상이므로(출고의 끝 = 배송의 시작) 세 쌍 중 둘이 공유 멤버 한 개로
302
+ * 침묵하고, 그 침묵한 쌍들이 **한 영역에서 만난다**. B-60 은 영역 안에 **무소속** 노드를 요구하는데
303
+ * 3중 영역의 도형은 대개 어느 한 그룹의 멤버다(표본 46 = 안 5 · 무소속 0 · 표본 49 = 안 2 · 무소속 0).
304
+ *
305
+ * ⚠ **대응이 B-60 과 정반대다** — B-60 은 무소속 노드를 멤버에 넣으면 풀리지만(대가 0), 3중은
306
+ * 멤버를 넓히면 상자가 커져 **더 겹친다**. 표본 49 가 그 예다: 에이전트가 `group.update` 로 멤버
307
+ * 하나를 선제 공유해 판정을 껐고 3중이 남았다. 수단은 단계 경계를 다시 잡는 것(그룹을 줄이거나 합치기)이다.
308
+ *
309
+ * 🔴 **비율로 가르지 말 것** — B-52 에서 두 번 기각됐다(표본 32 **72% 수용** · 표본 47 **70% 포기**).
310
+ * 「상자 겹침도 − 멤버 겹침도」 괴리도 **N20 에서 기각**됐다(수용한 표본 32 가 59.3%p 로 포기한
311
+ * 표본 49 의 59.1%p 보다 **높다**). 판별자는 비율이 아니라 **중복도**다.
312
+ *
313
+ * 근거(오탐): 실무 16문서 **0**(그룹 15) · 저장본 56종 **2**(`g16-base`·`g19-base`) · fixture 53종
314
+ * **3**(전부 `g16-edit*`). **해당 5건이 전부 사용자가 포기한 회차(표본 46·49)의 산출물이고 수용된
315
+ * 회차는 전량 0** 이다. B-60 과 합치면 포기 4회차(표본 44·46·47·49)를 전량 잡고 수용 2회차를
316
+ * 전량 통과시킨다.
317
+ *
318
+ * `groups` = 그 영역을 덮은 세 그룹(id 오름차순) · `shapes` = 영역 안의 흐름 노드(멤버 여부 무관).
319
+ */
320
+ | {
321
+ kind: 'group-stack-unreadable';
322
+ groups: [string, string, string];
323
+ shapes: string[];
324
+ }
325
+ /**
326
+ * 그룹의 테두리가 컨테이너(풀·확장 서브프로세스) 경계선과 **한 선으로 그려진다** — 두 선이 겹쳐
327
+ * 그룹 영역이 풀 테두리와 구분되지 않는다(표본 30 에서 사용자가 지목: "그룹이 레인선에 겹쳐서
328
+ * 출력되는 부분"). 배치 경로에서는 **결정적으로 발생**했다: 풀 상단에서 band 0 노드 중심까지 80,
329
+ * 노드 높이 80 이라 노드 상단 = 풀 상단 + 40 인데 그룹 패딩도 40 이라 정확히 얹힌다(그룹 5/5).
330
+ * 실무 문서에서는 그룹 24개 중 2개(8%)만 이 상태라 사람도 대개 떼어 놓는다. `container` = 그 컨테이너,
331
+ * `side` = 겹친 변.
332
+ */
333
+ | {
334
+ kind: 'group-on-container-boundary';
335
+ group: string;
336
+ container: string;
337
+ side: 'top' | 'bottom' | 'left' | 'right';
338
+ }
339
+ /**
340
+ * **두 그룹의 테두리가 한 선으로 그려진다**(B-45) — 단계 그룹이 옆으로 늘어설 때 결정적으로 난다:
341
+ * 상자 = 멤버 bbox ± 패딩(40) 이라 두 단계의 멤버가 정확히 **80**(= 2 × 패딩) 떨어지면 두 테두리가
342
+ * 정확히 겹친다. 표본 38 에서 사용자가 지목("그룹간에 선들이 겹쳐있는 부분")하고 두 쌍 모두 손으로 뗐다.
343
+ * 겹침이 아니라 **맞닿음**이라 `group-overlap-unintended`(양 축 5px 초과 겹침)는 이 형상을 못 본다.
344
+ *
345
+ * 근거(오탐): 실무 18문서의 그룹쌍 65 중 이 상태는 **0건** — 사람은 테두리를 겹쳐 두지 않는다.
346
+ * 우리 산출물에서는 fixture 10종(그룹 2개 이상) 중 **7종**에서 났다(관측 11건 전부 간격 0).
347
+ */
348
+ | {
349
+ kind: 'group-on-group-boundary';
350
+ groups: [string, string];
351
+ axis: 'x' | 'y';
352
+ }
353
+ /**
354
+ * **블랙박스 풀의 이름 텍스트**가 다른 요소의 외부 라벨과 겹쳤다(B-38) — 두 텍스트가 포개져 어느 쪽도
355
+ * 읽을 수 없는데, 종전에는 **아무 판정 채널도 이 텍스트를 보지 않았다**(DI 에 `BPMNLabel` 이 없고
356
+ * `auditTargetsOf` 는 컨테이너를 뺀다). G13 에서 사용자가 지목: 풀을 넓히자 이름이 중앙으로 따라
357
+ * 이동해 메시지 라벨을 덮었고 *"해당 문구는 선택 가능한 문구가 아니라서 에이전트가 판단이 될지 의문"*.
358
+ * 고치는 수단이 다르다 — 이름은 **옮길 수 없으므로**(위치 = 풀 중심 = 폭의 함수) 상대 라벨을 옮기거나
359
+ * 풀 기하를 바꾼다. `pool` = 그 참여자, `label` = 겹친 라벨의 소유자 id.
360
+ *
361
+ * 근거(오탐): 실무 13문서의 외부 라벨 302개 중 라벨끼리 겹친 쌍은 **0** 이다 — 사람은 텍스트를 겹쳐
362
+ * 두지 않는다. 우리 산출물에서도 이 겹침은 G10~G13 표본 전량 0건이다.
363
+ */
364
+ /**
365
+ * **그룹의 테두리가 흐름노드를 가로지른다**(B-65) — 도형이 상자 안도 밖도 아니어서 그 노드가 어느
366
+ * 단계의 일인지 그림만 보고는 읽을 수 없다. 표본 55 에서 사용자가 지목: *"그룹에 반만 걸친 요소"*
367
+ * (`변경 완료 수신` 이 `배송` 상자에 **49%** 만 덮였다 · `주문 변경 접수` 는 `접수` 상자에 80%).
368
+ * 근인은 A-4 의 x∪y bbox 다 — 멤버가 흩어지면 상자가 남의 노드 위로 자란다.
369
+ *
370
+ * 🔴 **판별자는 「양」 이 아니라 「객체 종류」 다.** 같은 형상이 **주석**일 때는 사람도 한다
371
+ * (실무 26문서의 주석 5건 · B-46 이 그것을 재서 **취향 축**으로 판정하고 finding 을 안 뒀다).
372
+ * **흐름노드**로 좁히면 사람은 **0 건**이다(실무 그룹 36 중 0 · 저장본 8문서 15건). 그래서 이 판정은
373
+ * `NON_STAGE_TYPES` 를 뺀 대상에만 건다 — **B-46 의 판정을 뒤집는 것이 아니라 그 대상 집합 밖이다.**
374
+ *
375
+ * `depth` = 테두리선이 도형을 가른 자리에서 **작은 쪽 조각의 px**. 실측 분포가 **0.5~4px**(테두리가
376
+ * 도형 가장자리를 스친다)와 **16~49px**(진짜로 반이 잘린다)로 갈리고 그 사이가 비어 있어
377
+ * `GROUP_CUT_MIN_DEPTH` 를 그 골짜기에 둔다. ⚠ 실무 기저는 바닥값이 **없어도 0** 이므로, 얕은 쪽이
378
+ * 실제로 지목되는 회차가 나오면 근거를 들고 낮출 것.
379
+ */
380
+ | {
381
+ kind: 'group-cuts-shape';
382
+ group: string;
383
+ shape: string;
384
+ side: 'top' | 'bottom' | 'left' | 'right';
385
+ depth: number;
386
+ } | {
387
+ kind: 'pool-name-over-label';
388
+ pool: string;
389
+ label: string;
50
390
  };
51
391
  /** 발견을 전후 비교(배치가 새로 만든 것 가려내기)할 수 있게 하는 안정 키. */
52
392
  export declare function geometryFindingKey(finding: GeometryFinding): string;
393
+ /**
394
+ * `processRef` 없는 참여자(= bpmn-js `isExpanded` 거짓)의 **이름 텍스트 사각형**. DI 에 없는 렌더 산물이라
395
+ * 여기서 산출한다 — 폭 근사는 주석 크기 산출과 같은 출처(`approximateTextWidth`)를 쓴다.
396
+ *
397
+ * 줄바꿈은 계산하지 않는다: 상자 폭이 풀 폭(1000px 이상)이라 실무·표본 전량에서 접히지 않았고, 명시 개행
398
+ * (`\n`)만 줄을 가른다. 접히는 이름이 관찰되면 그때 폭 기준 줄바꿈을 넣는다(조기 일반화 회피).
399
+ */
400
+ export declare function blackBoxNameRect(pool: GeometryShape, name: string): GeometryLabel;
401
+ export interface AxisSegment {
402
+ axis: 'h' | 'v';
403
+ /** 고정 좌표(h 면 y, v 면 x). */
404
+ at: number;
405
+ lo: number;
406
+ hi: number;
407
+ /** 진행 방향 부호(+ = 오른쪽/아래). */
408
+ dir: 1 | -1;
409
+ }
410
+ /** 간선의 축 정렬 구간들(웨이포인트 순서). 사선 구간은 뺀다. */
411
+ export declare function axisSegmentsOf(edge: GeometryEdge): AxisSegment[];
412
+ /**
413
+ * 두 간선의 **읽을 수 없는** 공선 중첩 — 방향이 반대(`opposite`)이거나, 같은 방향인데 종류가 다른
414
+ * (`mixed`: 실선 × 점선) 겹침. 가장 긴 것의 (축, 길이, 종류). 없으면 `null`. 같은 종류·같은 방향
415
+ * (합류·팬아웃)은 세지 않는다 — 사용자가 그대로 두는 형상이다(표본 4·7).
416
+ */
417
+ export declare function unreadableOverlapOf(a: GeometryEdge, b: GeometryEdge): {
418
+ axis: 'h' | 'v';
419
+ length: number;
420
+ kind: 'opposite' | 'mixed';
421
+ } | null;
422
+ /** 두 간선의 **반대 방향** 공선 중첩(종류 무관). `unreadableOverlapOf` 의 부분집합 — 기존 호출자 호환. */
423
+ export declare function oppositeOverlapOf(a: GeometryEdge, b: GeometryEdge): {
424
+ axis: 'h' | 'v';
425
+ length: number;
426
+ } | null;
427
+ /** 한 간선(후보 경로)이 다른 간선들과 만드는 반대 방향 중첩 수. */
428
+ export declare function oppositeOverlapCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
429
+ /** 한 간선(후보 경로)이 다른 간선들과 만드는 읽을 수 없는 중첩 수(반대 방향 + 종류 다름) — 보정의 목적 함수. */
430
+ export declare function unreadableOverlapCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
431
+ /** bpmn-js `LabelUtil.FLOW_LABEL_INDENT` — 라벨 중심이 선에서 떨어지는 거리(가로 구간은 위, 세로 구간은 오른쪽). */
432
+ export declare const FLOW_LABEL_INDENT = 15;
433
+ /**
434
+ * 갈림점 바로 옆에 붙은 라벨도 모호하다(다섯 번째 표본: 갈림 16px 아래의 '보완 요청' 을 사용자가 더
435
+ * 내렸다) — 공유 구간을 양쪽으로 이만큼 넓혀 본다. 배치(`planLabelPlacement`)도 같은 폭을 뺀다.
436
+ */
437
+ export declare const LABEL_FORK_MARGIN = 20;
438
+ /**
439
+ * `seg` 가 다른 간선의 구간과 공선 공유하는 부분들(≥ `OVERLAP_MIN_LENGTH`, 방향 무관) — 갈림 여백을
440
+ * 더해 돌려준다. 판정과 배치가 **같은 목록**을 써야 "옮겼는데 여전히 잡힌다"가 나지 않는다.
441
+ */
442
+ export declare function sharedSpansOf(seg: AxisSegment, otherSegments: readonly AxisSegment[]): Array<[number, number]>;
443
+ /**
444
+ * 이 간선의 라벨이 다른 간선과 **공선 공유하는 구간**(방향 무관, ≥ `OVERLAP_MIN_LENGTH`, 갈림 여백 포함)
445
+ * 위에 놓여 있으면 그 상대(id 최소)와 축을 돌려준다. 라벨이 없거나 고유 구간에 있으면 `null`.
446
+ */
447
+ export declare function sharedSegmentUnderLabel(edge: GeometryEdge, others: readonly GeometryEdge[]): {
448
+ other: string;
449
+ axis: 'h' | 'v';
450
+ } | null;
451
+ export declare function lineCrossesLabel(label: GeometryLabel, seg: AxisSegment): boolean;
452
+ /** 컨테이너 도형의 네 변을 축 정렬 구간으로 — 레인·풀 경계선. */
453
+ export declare function containerBoundarySegments(shape: GeometryShape): AxisSegment[];
454
+ /**
455
+ * 이 간선의 라벨을 가로지르는 선 — 자기 간선 구간 → 다른 간선 구간(id 순) → 컨테이너 경계선(id 순). 첫 것의 id.
456
+ * 없으면 `null`. 판정과 보정(routeRepair `planLabelLineClearance`)이 같은 술어를 쓴다.
457
+ */
458
+ export declare function lineThroughLabel(edge: GeometryEdge, scene: GeometryScene): string | null;
53
459
  /**
54
460
  * 이 간선이 관통하는 도형들(자기 끝점 제외). 판정과 **우회 보정**(routeRepair)이 같은
55
461
  * 술어를 써야 "고쳤다는데 판정은 그대로"가 나지 않는다.
@@ -57,8 +463,56 @@ export declare function geometryFindingKey(finding: GeometryFinding): string;
57
463
  * `targets` 는 컨테이너를 제외한 도형 목록 — 호출자가 한 번 걸러 넘긴다(반복 호출 비용).
58
464
  */
59
465
  export declare function crossedShapesOf(edge: GeometryEdge, targets: readonly GeometryShape[]): GeometryShape[];
60
- /** 컨테이너를 뺀 판정 대상 도형 — 관통·겹침 양쪽의 공통 모집단. */
466
+ /**
467
+ * 한 간선(또는 그 후보 경로)이 다른 간선들과 교차하는 수. **끝점을 공유하는 간선은 제외**한다 —
468
+ * 같은 노드에서 나가는 팬아웃·같은 노드로 드는 합류는 꺾이는 자리가 겹쳐 보여도 교차가 아니다.
469
+ */
470
+ export declare function edgeCrossCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
471
+ /**
472
+ * 교차 **비용**(B-18 ⓑ) — 같은 종류(실선×실선)의 교차는 2, 종류가 다른(실선×점선) 교차는 1. 여덟 번째 표본의
473
+ * 사용자 우선순위: "어쩔 수 없을 땐 크로스될 수밖에 없지만 가급적 **다른 종류의 선**과 크로스되는 게 구분이
474
+ * 좋다". 후보 선택의 교차 축은 이 비용으로 재고, 계측(`edgeCrossCount`·`stats`)은 개수 그대로 둔다.
475
+ */
476
+ export declare function crossingCost(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
477
+ /** 장면 전체의 교차 비용 합(쌍마다 같은 종류 2 · 다른 종류 1). */
478
+ export declare function sceneCrossingCost(scene: GeometryScene): number;
479
+ /** 장면 전체의 교차 쌍(id 오름차순 정렬, 결정론적). 길이가 곧 교차 수다. */
480
+ export declare function edgeCrossingPairsOf(scene: GeometryScene): Array<[string, string]>;
481
+ /**
482
+ * 교차 쌍을 **에이전트가 판단할 수 있는 재료로 분해**한다(B-43). 개수만으로는 "이 정도가 정상인가" 를
483
+ * 가릴 수 없고, 지금까지 세 회차에서 같은 마찰이 났다(표본 34·36 은 재료 없이도 결론이 맞았으나
484
+ * 표본 47 에서는 에이전트가 **판정을 보류**했다). 산출 비용은 0 이다 — 종류는 이미 `type` 에 있다.
485
+ *
486
+ * - `solidPairs` / `dashedPairs` / `mixedPairs` — 교차 쌍을 **선 스타일 조합**으로 가른다.
487
+ * `solidPairs`(실선×실선 = 시퀀스끼리)가 **배치를 다시 볼 신호**다 — 풀 횡단으로 설명되지 않는다.
488
+ * `mixedPairs`(실선×점선)는 풀을 건너는 메시지가 시퀀스를 지나는 정상 형상이 대부분이다.
489
+ * `dashedPairs`(점선×점선 = 메시지·데이터 연관끼리)는 **둘과 또 다르다** — 대개 장거리 데이터 연관이
490
+ * 여러 메시지를 지나는 형상이라 노드 배치가 아니라 저장소·연관의 열이 원인이다.
491
+ *
492
+ * 🔴 **축이 둘이었을 때 뜻이 어긋났다**(표본 48 실측) — `sameKind` 는 "같은 스타일끼리" 를 셌는데
493
+ * 주석·playbook 은 "실선끼리" 라고 불렀다. G18 의 `sameKind 2` 는 전부 `DA_chg × M_*`(점선×점선)였고
494
+ * 실선끼리는 **0** 이었는데, 에이전트가 이름을 믿고 "재볼 신호인 실선끼리 교차 2건" 으로 보고했다.
495
+ * 이름이 재료를 왜곡하면 판단이 그 위에 선다 ⇒ 축을 셋으로 갈라 이름과 뜻을 일치시킨다.
496
+ *
497
+ * - `crossPoolMessages` — 두 끝점 사이에 **다른 풀이 끼어 있는** 메시지플로우 수. 인접 풀끼리 주고받는
498
+ * 메시지는 아무것도 건너지 않지만 이것들은 지나가는 풀의 시퀀스와 **구조적으로** 만난다.
499
+ * playbook §6 의 "풀 횡단 메시지 1개당 교차 1" 감을 실제로 계산할 수 있게 하는 분모다(= `mixedPairs` 의).
500
+ */
501
+ export declare function edgeCrossingStatsOf(scene: GeometryScene): {
502
+ pairs: Array<[string, string]>;
503
+ solidPairs: number;
504
+ dashedPairs: number;
505
+ mixedPairs: number;
506
+ crossPoolMessages: number;
507
+ };
61
508
  export declare const auditTargetsOf: (scene: GeometryScene) => GeometryShape[];
509
+ /** 도형의 외부 이름 라벨을 가로지르는 선 — 간선 구간(id 순) → 컨테이너 경계선(id 순). 첫 것의 id, 없으면 `null`. */
510
+ export declare function lineThroughShapeLabel(shape: GeometryShape, scene: GeometryScene): string | null;
511
+ /** 장면의 외부 라벨 전부(도형 이름 라벨 + 간선 라벨)와 그 소유자 — 판정과 주석 배치(6단계 장애물)가 같은 목록을 쓴다. */
512
+ export declare function labelRectsOf(scene: GeometryScene): Array<{
513
+ owner: string;
514
+ rect: GeometryLabel;
515
+ }>;
62
516
  /**
63
517
  * 기하 판정 (순수). 결과는 **결정론적 순서**로 정렬해 반환한다 — 전후 diff·골든 비교·회귀
64
518
  * 고정이 전부 순서에 의존한다.
@@ -67,10 +521,6 @@ export declare const auditTargetsOf: (scene: GeometryScene) => GeometryShape[];
67
521
  * 에서 무시할 수준이고, 색인은 실제로 큰 문서가 관찰될 때 넣는다(조기 최적화 회피).
68
522
  */
69
523
  export declare function auditGeometry(scene: GeometryScene): GeometryFinding[];
70
- /**
71
- * moddle `definitions` → 장면. 입력은 `describeProcess` 와 같은 트리라 캔버스·헤드리스 양쪽
72
- * 에서 같은 것을 준다(A-6 입력 계약 동형).
73
- */
74
524
  export declare function sceneFromDefinitions(definitions: ModdleElement): GeometryScene;
75
525
  /** 저장 진실 기준 전수 판정 — 그 문서에 있는 **전부**(선재 결함 포함). */
76
526
  export declare function auditBpmnGeometry(definitions: ModdleElement): GeometryFinding[];
@@ -8,7 +8,7 @@ export type { DocumentIndex, ElementCategory } from './documentIndex';
8
8
  export { buildRegistryIndex } from './documentIndex';
9
9
  export { validateOps } from './validate';
10
10
  export { lowerOps, resolverServicesOf, type ResolverServices } from './lower';
11
- export { planEdgeRepair, planSegmentDetours, repairIntroducedCrossings } from './routeRepair';
11
+ export { forwardThreePointCandidates, planCrossPoolRoute, planEdgeRepair, planLabelLineClearance, planLabelPlacement, planMiddleSegmentShifts, planOppositeOverlapRepair, planSegmentDetours, planBackEdgeBoundaryClearance, repairIntroducedBoundaryHugging, repairIntroducedCrossings, repairIntroducedOppositeOverlaps, repairIntroducedSharedSegmentLabels, repairIntroducedLabelLines, repairIntroducedShapeLabelLines, planShapeLabelClearance, rerouteIntroducedCrossPoolEdges, } from './routeRepair';
12
12
  export interface ApplyBpmnOpsResult {
13
13
  ok: boolean;
14
14
  issues: ResolveIssue[];
@@ -21,5 +21,42 @@ export interface ApplyBpmnOpsResult {
21
21
  * 자기 배치 탓으로 읽고 무한 교정에 빠진다. 문서 전체 감사는 `auditBpmnGeometry`.
22
22
  */
23
23
  findings: GeometryFinding[];
24
+ /**
25
+ * 계측(B-14 ⓐ) — finding 이 아니다. 문서 **전체**의 간선 교차 수를 하강 전/후로 준다. 사람
26
+ * 마무리의 실체가 이 축이었다(확정 6 세 번째 표본: 교차 7 → 1)는 것이 드러난 뒤 도구가 그것을
27
+ * 볼 수 있게 한 채널. 배치가 교차를 얼마나 만들었는지 = `after - before`.
28
+ */
29
+ stats: {
30
+ edgeCrossings: {
31
+ before: number;
32
+ after: number;
33
+ /**
34
+ * 하강 뒤 남은 진성 교차 쌍(간선 id, 오름차순) — 개수만으로는 "이 정도가 정상인가"를 에이전트가
35
+ * 판단할 수 없다(G12 마찰: 교차 5 를 받고 어느 선끼리인지 몰라 배치 재검토를 못 했다).
36
+ * 쌍을 보면 한 메시지가 여러 시퀀스를 건너는 것인지(풀 횡단 = 정상) 배치가 엉킨 것인지 갈린다.
37
+ */
38
+ pairs: Array<[string, string]>;
39
+ /**
40
+ * **실선×실선**(시퀀스끼리) 교차 쌍 수(B-43) — 풀 횡단으로 설명되지 않으므로 **배치를 다시 볼 신호**다.
41
+ */
42
+ solidPairs: number;
43
+ /**
44
+ * **점선×점선**(메시지·데이터 연관끼리) 교차 쌍 수(B-43). 실선끼리와도 실선×점선과도 뜻이 다르다 —
45
+ * 대개 장거리 데이터 연관이 여러 메시지를 지나는 형상이라 원인이 노드 배치가 아니라 저장소·연관의 열이다.
46
+ * 🔴 종전 `sameKind` 는 이것과 `solidPairs` 를 **한 칸에 묶어** 이름이 뜻을 왜곡했다(표본 48).
47
+ */
48
+ dashedPairs: number;
49
+ /**
50
+ * **실선×점선** 교차 쌍 수(B-43) — 풀을 건너는 메시지가 시퀀스를 지나는 정상 형상이 대부분이다.
51
+ * 분모는 `crossPoolMessages` 이고, 그 몇 배인지를 본다.
52
+ */
53
+ mixedPairs: number;
54
+ /**
55
+ * 두 끝점 사이에 **다른 풀이 끼어 있는** 메시지플로우 수(B-43) — 그런 메시지는 지나가는 풀의
56
+ * 시퀀스와 구조적으로 만난다. playbook §6 의 "풀 횡단 메시지 1개당 교차 1" 감의 **분모**다.
57
+ */
58
+ crossPoolMessages: number;
59
+ };
60
+ };
24
61
  }
25
62
  export declare function applyBpmnOps(modeler: BpmnModelerLike, ops: readonly SymbolicOp[], lower?: typeof lowerOps): ApplyBpmnOpsResult;
@@ -6,7 +6,10 @@ import { default as ElementRegistry } from 'diagram-js/lib/core/ElementRegistry'
6
6
  import { default as Canvas } from 'diagram-js/lib/core/Canvas';
7
7
  import { default as AutoPlace } from 'diagram-js/lib/features/auto-place/AutoPlace';
8
8
  import { default as BpmnReplace } from 'bpmn-js/lib/features/replace/BpmnReplace';
9
+ import { Shape } from 'bpmn-js/lib/model/Types';
9
10
  import { SymbolicOp } from '../symbolicOp';
11
+ import { BwlSide } from '../../core/hints';
12
+ import { GeometryFinding } from '../geometryAudit';
10
13
  /** resolver 가 대면하는 bpmn-js 서비스 표면 — 하강은 이 묶음만 사용한다. */
11
14
  export interface ResolverServices {
12
15
  modeling: Modeling;
@@ -18,5 +21,45 @@ export interface ResolverServices {
18
21
  bpmnReplace: BpmnReplace;
19
22
  }
20
23
  export declare function resolverServicesOf(modeler: BpmnModelerLike): ResolverServices;
24
+ /**
25
+ * ── B-40. 주석 자리 산출 — **add·update·기존 주석 보정 공유** ──
26
+ *
27
+ * 선언 방위 → 실제 좌표(풀 안 클램프 B-20 ⓑ · 앵커 회피 · 형제·간선 B-21 · 외부 라벨 B-24 ⓑ 회피 ·
28
+ * 대안 방위 좌 우선 B-26 ⓐ). 힌트가 없으면 휴리스틱 자리를 같은 규칙으로 클램프·비낀다.
29
+ *
30
+ * 종전에는 이 전부가 `annotation.add` 루프 안에 인라인이었고 `annotation.update` 는 **원시 계산**만 했다.
31
+ * 표본 34 실측: 에이전트가 `annotation-over-label` 을 playbook §6 대로 `annotation.update` 로 고치려 하자
32
+ * 주석이 풀 위로 65px 나갔고(`shape-outside-container` 신규), **배치 1 과 완전히 같은 placement 를 다시
33
+ * 보내도 되돌아가지 않았다**(add 810 = 클램프 / update 775 = 원시값). 같은 입력이 경로에 따라 다른 좌표를
34
+ * 내는 것 자체가 결함이고, 규약대로 행동한 에이전트가 문서를 나쁘게 만들었다.
35
+ * B-33 → B-34 ⓐ 가 그룹 경계 보정에서 닫은 것과 같은 클래스다.
36
+ *
37
+ * `selfId` = 이미 존재하는 주석(update)의 자기 도형·자기 연관을 장애물에서 뺀다 — 안 빼면 늘 "막힘"이다.
38
+ */
39
+ export declare function annotationSpot(elementRegistry: ElementRegistry, anchor: {
40
+ x: number;
41
+ y: number;
42
+ width: number;
43
+ height: number;
44
+ }, dims: {
45
+ width: number;
46
+ height: number;
47
+ }, box: Shape, opts: {
48
+ placement?: {
49
+ side: BwlSide;
50
+ offset?: number | null;
51
+ } | null;
52
+ from?: {
53
+ x: number;
54
+ y: number;
55
+ };
56
+ selfId?: string;
57
+ }): {
58
+ x: number;
59
+ y: number;
60
+ };
21
61
  /** 검증 통과가 전제 — 참조 해소 실패는 여기서 프로그래밍 오류로 throw 되고 applyBpmnOps 가 롤백한다. */
22
62
  export declare function lowerOps(ops: readonly SymbolicOp[], services: ResolverServices): void;
63
+ /** 주석 도형이 소유자로 등장할 수 있는 finding 의 소유 도형 id. */
64
+ export declare function annotationOwnersOf(finding: GeometryFinding): readonly string[];
65
+ export declare function repairIntroducedAnnotationOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeShapeIds: ReadonlySet<string>): void;