@g1cloud/bpmn-modeler-next 5.0.0-beta.9 → 5.0.0-beta.90

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,28 @@
1
+ /**
2
+ * 주석 연관선의 가져오기 도킹 원점 (B-139) — 주석 쪽 끝점의 `original` 이 **주석 밖**이면 주석 중심으로 옮긴다.
3
+ *
4
+ * bpmn-js `ImportDockingFix` 는 가져올 때 연결선 끝점마다 `original`(도형이 움직일 때 끝점이 따라갈 기준)을
5
+ * 「끝 구간 연장선 × 도형 중심 십자선」 교점 중 중심에 가까운 쪽으로 잡는다. 납작한 주석(높이 30 안팎)에 얕은
6
+ * 사선 연관이 닿으면 그 교점이 **주석 밖**이고(이관 레거시 연관 끝점 ≈ 25%), 주석이나 앵커를 옮기면 끝점이 그
7
+ * 상대 위치를 따라가 주석 밖에 남는다 = 연관선이 주석에서 떨어진다(`edge-endpoint-detached`).
8
+ *
9
+ * 🔴 **resolver 가 아니라 가져오기에 건다** — 에이전트 하강의 안전망(B-98 재도킹)은 반려될 수 있고, **GUI 이동에는
10
+ * 안전망이 아예 없다**(표본 125 = 도구가 주석을 옮겨 연관이 얕아진 문서를 사람이 GUI 로 옮기다 끊었다). 캔버스 ·
11
+ * 헤드리스 양쪽 가져오기에 등록한다. 중심으로 두면 bpmn-js 의 의도(「중심 쪽 원점 → 앵커를 향한 면에 붙는다」)가
12
+ * 살아난다. 주석 안 원점 · 주석이 아닌 끝 · 연관이 아닌 연결선은 건드리지 않는다. 저장되는 DI 는 바뀌지 않는다
13
+ * (`original` 은 직렬화되지 않는다 — 다음 이동부터 다르다).
14
+ *
15
+ * ⚠ **bpmn-js 내부 이벤트(`bpmnElement.added`)와 `ImportDockingFix` 의 원점 규칙에 기댄다** — pin 상향 게이트 =
16
+ * `annotationImportDocking.browser.test.ts`(`translationLocaleCopy` 와 같은 처지).
17
+ */
18
+ type Any = any;
19
+ export declare class AnnotationImportDocking {
20
+ static $inject: string[];
21
+ constructor(eventBus: Any);
22
+ }
23
+ /** bpmn-js 모듈 — 서비스 이름 = `annotationImportDocking`. 헤드리스 기본 셋과 `BpmnModelerView` 가 붙인다. */
24
+ export declare const AnnotationImportDockingModule: {
25
+ __init__: string[];
26
+ annotationImportDocking: (string | typeof AnnotationImportDocking)[];
27
+ };
28
+ export {};
@@ -11,9 +11,17 @@ export declare function inferScriptLocale(text: string): ScriptInference;
11
11
  /**
12
12
  * 루트 판정의 근거.
13
13
  * `kept` 이미 있다 · `matched` 번역 칸과 같은 이름들의 공통 언어 · `script` 문자 체계가 한 언어로 모인다 ·
14
- * `ambiguous` 공통 언어가 여럿이고 문자 체계로도 못 고른다 · `mixed` 근거가 서로 다른 언어 · `no-evidence` 근거 없음
14
+ * `ambiguous` 공통 언어가 여럿이고 문자 체계로도 못 고른다 · `mixed` 근거가 서로 다른 언어 · `no-evidence` 근거 없음 ·
15
+ * `non-design` 근거가 설계 언어 아닌 언어를 가리킨다 — 루트로 쓰지 않는다(그 언어 이름은 요소 칸으로 · B-177)
15
16
  */
16
- export type RootLocaleReason = 'kept' | 'matched' | 'script' | 'ambiguous' | 'mixed' | 'no-evidence';
17
+ export type RootLocaleReason = 'kept' | 'matched' | 'script' | 'ambiguous' | 'mixed' | 'no-evidence' | 'non-design';
18
+ export interface LocaleBackfillOptions {
19
+ /**
20
+ * 그룹의 설계 언어 — 주면 그와 다른 루트는 쓰지 않는다(`non-design`). 없으면 종전 판정 그대로.
21
+ * 루트가 없으면 표시 판정이 이름을 설계 언어로 읽으므로, 루트는 설계 언어일 때만 말할 것이 있다.
22
+ */
23
+ designLocale?: string;
24
+ }
17
25
  /** 계획 항목이 가리키는 요소 — 미리보기 화면이 사람에게 보여 줄 최소 정보. */
18
26
  export interface LocaleBackfillElement {
19
27
  id: string;
@@ -55,7 +63,7 @@ export interface LocaleBackfillPlan {
55
63
  }>;
56
64
  }
57
65
  /** 언어 백필 계획 — 문서를 바꾸지 않는다. */
58
- export declare function planLocaleBackfill(definitions: Any): LocaleBackfillPlan;
66
+ export declare function planLocaleBackfill(definitions: Any, options?: LocaleBackfillOptions): LocaleBackfillPlan;
59
67
  /**
60
68
  * 계획을 문서에 쓴다 — 바꾼 곳 수를 돌려준다. **멱등**이고 계획 뒤 문서가 바뀌었어도 덮지 않는다:
61
69
  * 루트가 이미 있으면 루트를 건너뛰고, 요소는 지금도 「언어 기록 없음 + 이름이 계획 때 그대로」일 때만 쓴다.
@@ -0,0 +1,11 @@
1
+ type Any = any;
2
+ export declare class TranslationLocaleCopy {
3
+ static $inject: string[];
4
+ constructor(eventBus: Any);
5
+ }
6
+ /** bpmn-js 모듈 — 서비스 이름 = `translationLocaleCopy`. 헤드리스 기본 셋과 `BpmnModelerView` 가 붙인다. */
7
+ export declare const TranslationLocaleCopyModule: {
8
+ __init__: string[];
9
+ translationLocaleCopy: (string | typeof TranslationLocaleCopy)[];
10
+ };
11
+ export {};
@@ -0,0 +1,86 @@
1
+ import { ScriptKind } from './i18nBackfill';
2
+ type Any = any;
3
+ /** 번역 칸 하나의 상태 — `locale` 없음 = `xml:lang` 이 없는 칸. */
4
+ export interface TranslationCellState {
5
+ locale?: string;
6
+ body: string;
7
+ }
8
+ /** 요소 하나의 번역 칸 상태(문서 순서). */
9
+ export interface TranslationElementState {
10
+ /** moddle 타입(`bpmn:Task` 등). */
11
+ type: string;
12
+ cells: TranslationCellState[];
13
+ }
14
+ /** 문서 한 판본의 번역 칸 상태 — 요소 id → 상태(번역 칸이 있는 DI 평면 요소만). */
15
+ export type TranslationStates = Map<string, TranslationElementState>;
16
+ /** 문서 한 판본의 번역 칸 상태를 뽑는다(문서 무변경). */
17
+ export declare function collectTranslationStates(definitions: Any): TranslationStates;
18
+ interface IndexedState {
19
+ doc: string;
20
+ version: number;
21
+ id: string;
22
+ state: TranslationElementState;
23
+ }
24
+ /**
25
+ * 근거 색인 — 그룹 문서 전부의 판본(현재본 · 이력)을 담는다. 언어 칸이 하나도 없는 상태는 근거가 될 수 없어 담지 않는다.
26
+ * `doc` = 문서 식별자(호스트 문서 id) · `version` = 판본 번호(`history` 단계가 최근 판본부터 본다).
27
+ */
28
+ export declare class TranslationStateIndex {
29
+ private readonly byId;
30
+ private readonly byBodies;
31
+ add(doc: string, version: number, states: TranslationStates): void;
32
+ /** @internal */
33
+ sameId(id: string): IndexedState[];
34
+ /** @internal */
35
+ sameBodies(cells: TranslationCellState[]): IndexedState[];
36
+ }
37
+ /** 근거 단계 — `history` 같은 문서 이전 판본 · `lineage` 다른 문서 같은 요소 id · `bodies` id 무관 같은 본문 묶음. */
38
+ export type TranslationLocaleRestoreSource = 'history' | 'lineage' | 'bodies';
39
+ /** 계획 항목이 가리키는 요소. */
40
+ export interface TranslationLocaleRestoreTarget {
41
+ id: string;
42
+ /** moddle 타입. */
43
+ type: string;
44
+ }
45
+ export interface TranslationLocaleRestorePlan {
46
+ /** 되살릴 칸 — 적용은 그 본문의 무언어 칸이 남아 있고 요소에 그 언어가 아직 없을 때만 쓴다. */
47
+ restores: Array<TranslationLocaleRestoreTarget & {
48
+ source: TranslationLocaleRestoreSource;
49
+ cells: Array<{
50
+ body: string;
51
+ locale: string;
52
+ }>;
53
+ }>;
54
+ /** 맞는 근거 상태들이 서로 다른 배정을 낸다 — 쓰지 않는다(사람 확인). */
55
+ ambiguous: Array<TranslationLocaleRestoreTarget & {
56
+ source: TranslationLocaleRestoreSource;
57
+ }>;
58
+ /** 근거 언어가 이미 요소에 있다 — 손실 뒤 그 언어를 다시 썼다 · 무언어 칸은 낡은 문구(되살리지 않는다). */
59
+ stale: Array<TranslationLocaleRestoreTarget & {
60
+ body: string;
61
+ locale: string;
62
+ }>;
63
+ /**
64
+ * 후보인데 근거 상태가 없다 — 쓰지 않는다. `script` = 문자 체계(사람 확인 · 문자 판정 쓰기는 범위 밖).
65
+ * 문서에 나오는 언어를 그 요소가 이미 다 가졌으면 되살릴 언어가 없으므로 싣지 않는다(언어 칸 옆의 잔재 칸 — 로컬 실측 7).
66
+ */
67
+ unresolved: Array<TranslationLocaleRestoreTarget & {
68
+ body: string;
69
+ script: ScriptKind;
70
+ }>;
71
+ }
72
+ export interface TranslationLocaleRestoreContext {
73
+ /** 이 문서의 식별자 — 색인의 `doc` 과 같은 값. */
74
+ doc: string;
75
+ index: TranslationStateIndex;
76
+ /** 문서 이력 첫 판본의 상태 — 그 뒤에 생긴 무언어 칸만 후보다. 없으면 칸 전부가 무언어인 요소만 본다. */
77
+ baseline?: TranslationStates;
78
+ }
79
+ /** 언어 복구 계획 — 문서를 바꾸지 않는다. */
80
+ export declare function planTranslationLocaleRestore(definitions: Any, ctx: TranslationLocaleRestoreContext): TranslationLocaleRestorePlan;
81
+ /**
82
+ * 계획을 문서에 쓴다 — 바꾼 칸 수를 돌려준다. **멱등**이고 계획 뒤 문서가 바뀌었어도 덮지 않는다: 그 본문의 무언어 칸이
83
+ * 남아 있고 요소에 그 언어가 아직 없을 때만 그 칸에 `xml:lang` 을 단다.
84
+ */
85
+ export declare function applyTranslationLocaleRestore(definitions: Any, plan: TranslationLocaleRestorePlan): number;
86
+ export {};
@@ -44,6 +44,11 @@ export type ApplyBatchOutcome =
44
44
  findings: GeometryFinding[];
45
45
  /** 간선 교차 계측(B-14 ⓐ) — 문서 전체, 하강 전/후. finding 이 아니다. */
46
46
  stats: ApplyBpmnOpsResult['stats'];
47
+ /**
48
+ * 이 배치가 bpmn-js 에 **무작위로 만들게 한 id** — 생성 순서 그대로(`recordGeneratedIds` 옵션일 때만).
49
+ * 열린 에디터의 턴 끝 재실행(G4 B′ · `replayAgentTurn`)이 같은 순서로 받아 써야 산출이 바이트로 같아진다.
50
+ */
51
+ generatedIds?: string[];
47
52
  } | {
48
53
  status: 'conflict';
49
54
  baseVersion: number;
@@ -60,6 +65,11 @@ export interface ApplyBpmnOpBatchOptions {
60
65
  * 로 문서 언어 전부를 받고, 없으면 평문만 받는다(`applyBpmnOps` 의 같은 옵션).
61
66
  */
62
67
  i18n?: BpmnDocumentLocales;
68
+ /**
69
+ * 생성 id 를 순서대로 기록해 `applied.generatedIds` 로 낸다(G4 B′) — 생성 자체는 바꾸지 않는다(관찰만).
70
+ * 서버 집행 코어가 GUI 채팅 턴의 배치에 켠다. 기본 끔(응답 형태 종전 그대로).
71
+ */
72
+ recordGeneratedIds?: boolean;
63
73
  }
64
74
  /**
65
75
  * 배치를 캔버스에 적용하고 영속 페이로드를 낸다.
@@ -70,4 +80,4 @@ export interface ApplyBpmnOpBatchOptions {
70
80
  * 적용은 단일 undo 에피소드다(`applyBpmnOps` → `command/undoEpisode`) — 배치 수십 op 가
71
81
  * 사용자 undo 1회로 되감긴다.
72
82
  */
73
- export declare function applyBpmnOpBatch(modeler: BpmnDocumentModelerLike, batch: BpmnOpBatch, { currentVersion, i18n }: ApplyBpmnOpBatchOptions): Promise<ApplyBatchOutcome>;
83
+ export declare function applyBpmnOpBatch(modeler: BpmnDocumentModelerLike, batch: BpmnOpBatch, { currentVersion, i18n, recordGeneratedIds: record }: ApplyBpmnOpBatchOptions): Promise<ApplyBatchOutcome>;
@@ -0,0 +1,36 @@
1
+ import { BpmnDocumentModelerLike } from '../core/modelerLike';
2
+ import { BpmnDocumentLocales } from '../adapter/i18n';
3
+ import { SymbolicOp } from './symbolicOp';
4
+ /** 턴의 배치 하나 — 서버가 커밋한 순서대로. `generatedIds` = 서버 `applied.generatedIds`(`recordGeneratedIds`). */
5
+ export interface BpmnReplayBatch {
6
+ ops: SymbolicOp[];
7
+ generatedIds: string[];
8
+ }
9
+ /**
10
+ * 재실행 결과. `diverged` 면 문서는 **재실행 전 상태로 되감겨 있다** — 호스트는 서버 XML 로 reload 한다.
11
+ * - `unsupported` — 이 modeler 에 헤드리스 동등 실행 환경이 없다(`HeadlessReplayModule` 미탑재) · 배치가 없다
12
+ * - `rejected` — 배치가 검증 · 하강에서 거부됐다(서버에서는 통과한 배치다 — 시작 상태가 이미 달랐다)
13
+ * - `ids` — 생성 id 를 다 못 받아 썼다 · 남았다(하강 경로가 서버와 갈렸다)
14
+ * - `xml` — 끝까지 갔지만 바이트가 다르다
15
+ * - `error` — 예외
16
+ * `ms` = 재실행 소요(비교 포함) — 폴백률 · 비용 관측용(결정 §7).
17
+ */
18
+ export type ReplayAgentTurnOutcome = {
19
+ status: 'replayed';
20
+ ms: number;
21
+ } | {
22
+ status: 'diverged';
23
+ reason: 'unsupported' | 'rejected' | 'ids' | 'xml' | 'error';
24
+ /** 사람이 읽을 단서(거부 issue 요약 · 예외 문장 · id 수). */
25
+ detail?: string;
26
+ ms: number;
27
+ };
28
+ export interface ReplayAgentTurnOptions {
29
+ /** 서버가 이 턴 마지막 배치로 커밋한 XML — 판정 기준. */
30
+ expectedXml: string;
31
+ /** 그룹 문서 언어(B-4 ④) — 서버와 같은 값이어야 같은 검증 · 하강을 탄다. */
32
+ i18n?: BpmnDocumentLocales;
33
+ }
34
+ export declare function replayAgentTurn(modeler: BpmnDocumentModelerLike & {
35
+ get<T>(name: string, strict?: boolean): T;
36
+ }, batches: readonly BpmnReplayBatch[], { expectedXml, i18n }: ReplayAgentTurnOptions): Promise<ReplayAgentTurnOutcome>;
@@ -0,0 +1,18 @@
1
+ import { BpmnModelerLike } from '../core/modelerLike';
2
+ /** `run` 동안 생성된 id 를 순서대로 모은다 — 생성 자체는 바꾸지 않는다(관찰만). */
3
+ export declare function recordGeneratedIds<T>(modeler: BpmnModelerLike, run: () => T): {
4
+ value: T;
5
+ generatedIds: string[];
6
+ };
7
+ /**
8
+ * `run` 동안 생성을 `generatedIds` 순서로 대신한다. 다음 id 가 없거나 · 접두가 다르거나 · 이미 쓰인 id 면
9
+ * **무작위 생성으로 떨어지고 `missed` 를 센다** — 하강을 멈추지 않는다(판정은 호출자가 바이트 비교로 한다).
10
+ *
11
+ * @returns `fed` = 주입한 수 · `missed` = 떨어진 수 · `remaining` = 쓰지 않고 남은 수(셋 다 0 · 0 · 0 이 아니면 갈렸다)
12
+ */
13
+ export declare function feedGeneratedIds<T>(modeler: BpmnModelerLike, generatedIds: readonly string[], run: () => T): {
14
+ value: T;
15
+ fed: number;
16
+ missed: number;
17
+ remaining: number;
18
+ };
@@ -514,6 +514,58 @@ export type GeometryFinding =
514
514
  edge: string;
515
515
  shape: string;
516
516
  distance: number;
517
+ }
518
+ /**
519
+ * 🔴 **다이아몬드 합류가 자기 선행보다 앞(왼쪽)에 그려져 그 선이 거꾸로 든다**(B-213 · 관측 전용). 합류 = 비루프
520
+ * 선행(합류에서 다시 닿지 않는 출발) 둘 이상 · 다이아몬드 = 그 선행들이 공통 조상을 가진다(같은 분기에서 갈라진
521
+ * 경로가 만난다) · 앞 = 합류 중심 x 가 **선행 상자의 왼쪽 끝보다 왼쪽** — 선행에서 합류로 가는 선이 **반드시** 왼쪽으로
522
+ * 간다. ⚠ 중심끼리 비교하면 합류가 선행 바로 위에 조금 비켜 있어 곧은 수직선으로 잇는 자리까지 잡는다(같은 열이다).
523
+ * `edge` = 그 선행에서 합류로 드는 시퀀스.
524
+ *
525
+ * 원인은 2단계 탐욕 배치가 합류를 **처음 처리된 유입의 다음 열**에 세운 것이었다(분기 → 합류 직결 출구를 긴 갈래보다
526
+ * 먼저 선언 — 원격 반품 문서 두 풀). 하강은 B-213 이 고쳤고 이 판정은 그 밖의 경로(사람 편집 · 외부 도구 · 다이아몬드가
527
+ * 아닌 갱신)를 보는 안전망이다.
528
+ *
529
+ * 🔴 **다이아몬드로 좁히는 것이 판별자다**(N20) — 사람 레거시 다이아몬드 합류 **0/133**(갈래 깊이 차 ≥ 2 가 39% 인데도)
530
+ * · 우리 산출 0/69 인데, 다이아몬드가 아닌 합류(메시지로 시작하는 수신 태스크가 왼쪽 합류로 드는 꼴 등)는 사람도
531
+ * 6/41 을 그렇게 둔다. ⚠ **관측 전용인 이유** — 채택 게이트에 넣으면 보정 단계의 제약이 되는데 게이트마다 반사실을
532
+ * 거치지 않았다(모듈 머리 주석).
533
+ */
534
+ | {
535
+ kind: 'merge-before-predecessor';
536
+ merge: string;
537
+ edge: string;
538
+ }
539
+ /**
540
+ * 🔴 **연결선이 자기 끝점 도형 안을 지난다**(B-189 · 관측 전용). `edge-crosses-shape` 는 끝점 도킹 때문에 끝점을 통째로
541
+ * 면책해 이 꼴이 침묵했다 — 원격 BSP `Flow_g17` 은 출발 게이트웨이를 세로로 꿰뚫는데 판정은 자기 라벨의 `label-over-line`
542
+ * 으로만 샜다. 술어 = `piercesOwnEndpoint`(실형상 · `CROSS_INSET` · 도킹점은 경계 위라 안 걸린다) · `shape` = 꿰뚫린 끝점.
543
+ * 시퀀스 · 메시지만 본다 — 연관선은 끝 도킹의 반올림으로 표본 하나가 걸린다(원격 실문서 1/23).
544
+ *
545
+ * 사람 문서 기저 **0/4,244**(레거시 이관 161 · 실무 26 · N20). 하강의 알려진 생성 경로(행 이동)는 B-189 ⓑ 가 다시 깔고,
546
+ * 이 판정은 그 밖의 경로(사람 편집 · 외부 도구 · 이전 판이 남긴 선)를 보는 안전망이다. ⚠ **관측 전용인 이유** — 채택
547
+ * 게이트에 넣으면 보정 단계의 제약이 되는데 게이트마다 반사실을 거치지 않았다(모듈 머리 주석).
548
+ */
549
+ | {
550
+ kind: 'edge-crosses-own-endpoint';
551
+ edge: string;
552
+ shape: string;
553
+ }
554
+ /**
555
+ * 🔴 **참여자 끝 메시지가 끝점 노드 열을 막은 자기 풀 도형을 피해 돌아간다**(B-226 · 관측 전용). 술어 = 메시지 한 끝이 참여자 ·
556
+ * 경로 4점 이상 · 그 참여자가 노드 중심 열을 덮는다 · 노드 중심의 수직선(노드 면 ↔ 참여자 면)을 노드와 **같은 풀**의 다른 도형이
557
+ * 막는다(`shape` = 노드에 가장 가까운 것). 갈래 band 행 · 다른 레인 노드가 같은 열에 쌓이면 보정이 6점 U 로 감싸는데 그 우회는
558
+ * 어느 kind 로도 안 잡혀 저작 에이전트가 **못 봤다**(원격 Codex 저작 넷 = 응답의 판정 · 교차는 정확히 옮기고 이 우회는 한 번도 말하지
559
+ * 않았다). 사람이 쓰는 수단 = 막는 노드를 다음 열로.
560
+ *
561
+ * 사람 문서 기저 **0**(레거시 이관 161 · 실무 26 문서 · N20). 하강은 B-226 이동이 고치고(재맞춤 뒤 막는 노드 + 하류를 다음 열로),
562
+ * 이 판정은 그 이동이 정당하게 반려한 나머지(한 열 셋 쌓임 · 교환)와 그 밖의 경로를 보인다. ⚠ **관측 전용인 이유** — 채택 게이트에
563
+ * 넣으면 보정 단계의 제약이 되는데 게이트마다 반사실을 거치지 않았다(모듈 머리 주석).
564
+ */
565
+ | {
566
+ kind: 'message-detours-own-pool-shape';
567
+ edge: string;
568
+ shape: string;
517
569
  };
518
570
  /** 발견을 전후 비교(배치가 새로 만든 것 가려내기)할 수 있게 하는 안정 키. */
519
571
  export declare function geometryFindingKey(finding: GeometryFinding): string;
@@ -605,6 +657,15 @@ export declare function lineThroughLabel(edge: GeometryEdge, scene: GeometryScen
605
657
  * `targets` 는 컨테이너를 제외한 도형 목록 — 호출자가 한 번 걸러 넘긴다(반복 호출 비용).
606
658
  */
607
659
  export declare function crossedShapesOf(edge: GeometryEdge, targets: readonly GeometryShape[]): GeometryShape[];
660
+ /**
661
+ * ── B-189. 간선이 **자기 끝점** 안을 지나는가 ──
662
+ *
663
+ * `crossedShapesOf` 는 끝점 도킹이 경계에 붙어서 끝점을 **통째로** 건너뛴다 — 그 면책이 도킹 너머의 자기 관통까지 덮었다
664
+ * (B-141 · 원격 BSP `Flow_g17` = 출발 게이트웨이를 세로로 꿰뚫는데 판정은 자기 라벨로만 샜다). 여기서는 끝점을 **실형상**
665
+ * (게이트웨이 = 마름모 · 이벤트 = 원 · 그 밖 = 상자)을 `CROSS_INSET` 만큼 줄인 안쪽으로 재고 경로를 2px 간격으로 짚는다 —
666
+ * 도킹점은 경계 위라 안 걸린다. 사람 문서 기저 = **0/4,244**(레거시 이관 161 · 실무 26 · 시퀀스 + 메시지 · N20).
667
+ */
668
+ export declare function piercesOwnEndpoint(waypoints: readonly GeometryPoint[], endpoints: ReadonlyArray<Pick<GeometryShape, 'type' | 'x' | 'y' | 'width' | 'height'> | undefined>): boolean;
608
669
  /**
609
670
  * 선분과 도형 **실형상**(게이트웨이 = 마름모 · 이벤트 = 원 · 그 밖 = 사각형) 사이 최소 거리. 겹치면 0.
610
671
  * N20 계측기 `n20-edge-graze.py` 와 같은 정의다 — 기저(사람 26종 · 수용 회차 최종 67)가 이 거리로 재였다.
@@ -613,6 +674,8 @@ export declare function segmentShapeClearance(a: GeometryPoint, b: GeometryPoint
613
674
  /**
614
675
  * 한 간선(또는 그 후보 경로)이 다른 간선들과 교차하는 수. **끝점을 공유하는 간선은 제외**한다 —
615
676
  * 같은 노드에서 나가는 팬아웃·같은 노드로 드는 합류는 꺾이는 자리가 겹쳐 보여도 교차가 아니다.
677
+ * ⚠ `segmentsCross` 가 이미 접촉을 안 세므로 이 제외가 실제로 막는 것은 **내부 진성 교차**뿐이다(B-146) —
678
+ * 그쪽은 `sharedEndpointCrossingPairsOf` 가 따로 센다. 이 제외를 풀지 말 것: 위에 라우팅 채택 게이트가 선다.
616
679
  */
617
680
  export declare function edgeCrossCount(edge: GeometryEdge, others: readonly GeometryEdge[]): number;
618
681
  /**
@@ -625,6 +688,18 @@ export declare function crossingCost(edge: GeometryEdge, others: readonly Geomet
625
688
  export declare function sceneCrossingCost(scene: GeometryScene): number;
626
689
  /** 장면 전체의 교차 쌍(id 오름차순 정렬, 결정론적). 길이가 곧 교차 수다. */
627
690
  export declare function edgeCrossingPairsOf(scene: GeometryScene): Array<[string, string]>;
691
+ /**
692
+ * **끝점을 공유하는** 쌍의 진성 교차(B-146 ⓐ′ · 관측 전용) — `edgeCrossingPairsOf` 가 통째로 빼는 쌍 중
693
+ * 간선 **내부**에서 가로지르는 것. 둘을 합치면 장면의 진성 교차 전량이고 겹치는 쌍은 없다.
694
+ *
695
+ * 표본 98 에서 사람이 고친 교차가 이것이었다 — 게이트웨이 → 오류 노드 갈래와 오류 노드 → 상류 되돌아감이
696
+ * 오류 노드를 함께 끝점으로 가져 판정·계수가 모두 침묵했다(사람은 두 간선의 출입 변을 맞바꿨다).
697
+ * 🔴 **라우팅·채택 게이트의 입력이 아니다** — `edgeCrossCount` 의 제외를 바꾸면 그 위의 우회 채택·교차 보정과
698
+ * `--check` 기대값이 함께 움직인다. 이 함수는 보이게만 한다.
699
+ * ⚠ 「공유」는 각 어댑터의 `endpoints` 를 따른다 — 데이터 연관은 두 어댑터가 다르게 담는다(저장본 = 저장소만 ·
700
+ * 캔버스 = 활동 + 저장소).
701
+ */
702
+ export declare function sharedEndpointCrossingPairsOf(scene: GeometryScene): Array<[string, string]>;
628
703
  /**
629
704
  * 교차 쌍을 **에이전트가 판단할 수 있는 재료로 분해**한다(B-43). 개수만으로는 "이 정도가 정상인가" 를
630
705
  * 가릴 수 없고, 지금까지 세 회차에서 같은 마찰이 났다(표본 34·36 은 재료 없이도 결론이 맞았으나
@@ -677,6 +752,16 @@ export declare function shapesUnderLabel(label: GeometryLabel, scene: GeometrySc
677
752
  shape: string;
678
753
  depth: number;
679
754
  }>;
755
+ /**
756
+ * **B-91 ⓐ — 풀 × 풀 겹침 쌍.** 판정(`pool-overlap`)과 resolver 채택 게이트(B-174 ⓑ — ⓒ 막힌 열)가 **같은 정의**를 쓴다.
757
+ *
758
+ * 🔴 **양을 재지 않는다 — 겹치는가만 본다**(오탐 기저 0/97). 접촉(경계가 닿는 것)은 겹침이 아니다.
759
+ * `contained` = 한쪽이 다른 쪽을 통째로 덮었다.
760
+ */
761
+ export declare function poolOverlapsOf(shapes: readonly Pick<GeometryShape, 'id' | 'type' | 'x' | 'y' | 'width' | 'height'>[]): Array<{
762
+ pools: [string, string];
763
+ contained: boolean;
764
+ }>;
680
765
  /**
681
766
  * 기하 판정 (순수). 결과는 **결정론적 순서**로 정렬해 반환한다 — 전후 diff·골든 비교·회귀
682
767
  * 고정이 전부 순서에 의존한다.
@@ -686,7 +771,7 @@ export declare function shapesUnderLabel(label: GeometryLabel, scene: GeometrySc
686
771
  */
687
772
  export interface AuditGeometryOptions {
688
773
  /**
689
- * **관측 전용 kind** 까지 낸다(현재 = `edge-along-group-boundary` · `label-over-shape`) — 판정 표면(배치 응답 · 문서 전체 ·
774
+ * **관측 전용 kind** 까지 낸다(현재 = `edge-along-group-boundary` · `label-over-shape` · `merge-before-predecessor` · `edge-crosses-own-endpoint` · `message-detours-own-pool-shape`) — 판정 표면(배치 응답 · 문서 전체 ·
690
775
  * GUI 패널)만 켠다. resolver 채택 게이트는 켜지 않는다(모듈 머리 주석 — 켜면 게이트 제약이 된다).
691
776
  */
692
777
  includeObservationOnly?: boolean;
@@ -0,0 +1,12 @@
1
+ /** 주석 글자 폭 근사(12px) — 위 ①. */
2
+ export declare function annotationTextWidth(text: string): number;
3
+ /** `maxWidth`(여백을 뺀 글자 칸)에서 diagram-js 가 낼 줄들. */
4
+ export declare function wrapAnnotationText(text: string, maxWidth: number, measure?: (text: string) => number): string[];
5
+ /**
6
+ * 주석 상자 크기 — `texts` = 그 주석이 그려질 문구 전부(다국어 그룹 = 문서 언어마다 · 그 밖 = 평문 하나). 폭은 가장 긴
7
+ * 줄 + 1px(근사가 칸과 같으면 엄격 비교가 줄을 바꾼다) 을 상한까지, 넘으면 줄바꿈 수로 높이를 늘린다.
8
+ */
9
+ export declare function annotationBoxSize(texts: readonly string[]): {
10
+ width: number;
11
+ height: number;
12
+ };
@@ -9,6 +9,13 @@ export interface DocumentIndex {
9
9
  categoryOf(id: string): ElementCategory | undefined;
10
10
  /** 참여자에 속한 레인 id 목록 — **y순(위→아래)** 계약. lanes 재선언 diff 의 순서 비교 기준 */
11
11
  lanesOf(participantId: string): readonly string[];
12
+ /**
13
+ * 참여자에 속한 레인 id 목록 — **선언순**(laneSet 순서 = `describe` 가 내는 순서 · A-6). lanes 재선언은 이 순서도
14
+ * 받는다(B-153 — 선언순 ≠ y순 풀에서 `describe` 순서를 그대로 쓴 재선언이 거부됐다 · 레거시 레인 풀 19/50).
15
+ * 중첩 레인(`childLaneSet`)은 부모 뒤에 깊이 우선으로 잇는다(`lanesOf` 와 같은 집합).
16
+ * 선택 — 공개 타입이라 외부 구현을 깨지 않으려고 둔다(없으면 y순만 받는 종전 동작).
17
+ */
18
+ laneDeclarationOrderOf?(participantId: string): readonly string[];
12
19
  /** 플로우 노드가 속한 참여자 id (풀 없는 단일 프로세스 문서면 undefined) */
13
20
  participantOf(nodeId: string): string | undefined;
14
21
  /**
@@ -20,6 +20,9 @@ export interface ApplyBpmnOpsResult {
20
20
  *
21
21
  * **전후 diff 라 선재 결함은 담기지 않는다** — 남의 결함까지 돌려주면 에이전트가 그것을
22
22
  * 자기 배치 탓으로 읽고 무한 교정에 빠진다. 문서 전체 감사는 `auditBpmnGeometry`.
23
+ * 🔴 **예외 하나(B-219) — 이 배치가 앵커 대비 옮긴 주석이 가진 판정은 선재 키여도 담는다.** 판정 키에 위치가 없어
24
+ * 옮긴 주석이 같은 이름표 · 같은 주석 위에 남으면 「선재」로 걸러졌다 — 에이전트가 고치려고 옮긴 결과를 응답으로 못 봤다
25
+ * (원격 e92509 = 좌표를 손으로 쟀다 · fixture g12-12 = 두 번 연속 옮겼는데 둘 다 침묵). 그 관계는 이 배치가 만든 것이다.
23
26
  */
24
27
  findings: GeometryFinding[];
25
28
  /**
@@ -58,6 +61,17 @@ export interface ApplyBpmnOpsResult {
58
61
  */
59
62
  crossPoolMessages: number;
60
63
  };
64
+ /**
65
+ * **끝점을 공유하는** 간선끼리의 내부 진성 교차(B-146 ⓐ′) — 위 `edgeCrossings` 가 세지 않는 쌍이다(둘을 합치면
66
+ * 진성 교차 전량 · 겹치는 쌍 없음). 표본 98 에서 사람이 고친 교차가 이것이었다(게이트웨이 → 오류 노드 갈래 ×
67
+ * 오류 노드 → 상류 되돌아감). **관측 전용** — 보정 단계의 채택 게이트는 이 값을 보지 않는다.
68
+ */
69
+ sharedEndpointCrossings: {
70
+ before: number;
71
+ after: number;
72
+ /** 하강 뒤 남은 쌍(간선 id, 오름차순). */
73
+ pairs: Array<[string, string]>;
74
+ };
61
75
  };
62
76
  }
63
77
  /** 배치 적용 옵션 — 문서 밖에서만 알 수 있는 입력. */
@@ -10,8 +10,10 @@ import { default as SpaceTool } from 'diagram-js/lib/features/space-tool/SpaceTo
10
10
  import { Shape } from 'bpmn-js/lib/model/Types';
11
11
  import { SymbolicOp } from '../symbolicOp';
12
12
  import { ResolveIssue } from './issues';
13
+ import { IrSequenceFlow } from '../../core/ir';
13
14
  import { BwlSide } from '../../core/hints';
14
15
  import { GeometryFinding, GeometryScene } from '../geometryAudit';
16
+ import { LocaleWrite } from './localeTexts';
15
17
  /** resolver 가 대면하는 bpmn-js 서비스 표면 — 하강은 이 묶음만 사용한다. */
16
18
  export interface ResolverServices {
17
19
  modeling: Modeling;
@@ -29,7 +31,7 @@ export declare function resolverServicesOf(modeler: BpmnModelerLike): ResolverSe
29
31
  * ── B-40. 주석 자리 산출 — **add·update·기존 주석 보정 공유** ──
30
32
  *
31
33
  * 선언 방위 → 실제 좌표(풀 안 클램프 B-20 ⓑ · 앵커 회피 · 형제·간선 B-21 · 외부 라벨 B-24 ⓑ 회피 ·
32
- * 대안 방위 좌 우선 B-26 ⓐ). 힌트가 없으면 휴리스틱 자리를 같은 규칙으로 클램프·비낀다.
34
+ * 대안 방위 좌 우선 B-26 ⓐ). 힌트가 없는 주석은 add 가 `top` 으로 정규화해 넘긴다(B-2 — 종전 휴리스틱 분기는 걷어냈다).
33
35
  *
34
36
  * 종전에는 이 전부가 `annotation.add` 루프 안에 인라인이었고 `annotation.update` 는 **원시 계산**만 했다.
35
37
  * 표본 34 실측: 에이전트가 `annotation-over-label` 을 playbook §6 대로 `annotation.update` 로 고치려 하자
@@ -39,6 +41,14 @@ export declare function resolverServicesOf(modeler: BpmnModelerLike): ResolverSe
39
41
  * B-33 → B-34 ⓐ 가 그룹 경계 보정에서 닫은 것과 같은 클래스다.
40
42
  *
41
43
  * `selfId` = 이미 존재하는 주석(update)의 자기 도형·자기 연관을 장애물에서 뺀다 — 안 빼면 늘 "막힘"이다.
44
+ *
45
+ * **B-144** — 앵커가 레인 안이면 클램프 상자와 대안 방위의 `inside` 를 **앵커 레인 대역**으로 좁힌다(풀 클램프와
46
+ * 같은 규칙 — 레인 경계에서 클램프하고 앵커와 겹치면 오른쪽으로 비낀다). 🔴 **레인 안에 막히지 않은 자리가 없으면
47
+ * 종전대로 풀 안 빈 자리(이웃 레인일 수 있다)로 간다** — 레인 소속을 막힌 자리(겹침·관통)와 바꾸지 않는다
48
+ * (반사실: 레인 안 막힌 자리로 두는 안은 findings 가 는 케이스가 두 배였다 · 66 vs 33 / 방위 합성 6,208).
49
+ * 그것도 없으면 레인 안 선언 자리(막힘 — 판정기가 보고한다). ⚠ 잔존 대가 = 그 폴백의 이웃 레인 착지(방위 합성
50
+ * 0.6% — 사람 기저 0.4~1.0% 수준) · 레인 클램프가 오른쪽 비끼기를 더 자주 발동시켜 **가로로 풀 밖** 착지가
51
+ * 12 → 16(그 분기가 풀 우변을 넘는 것은 이 축 이전부터다 — 원장 B-144 곁).
42
52
  */
43
53
  export declare function annotationSpot(elementRegistry: ElementRegistry, anchor: {
44
54
  x: number;
@@ -49,19 +59,35 @@ export declare function annotationSpot(elementRegistry: ElementRegistry, anchor:
49
59
  width: number;
50
60
  height: number;
51
61
  }, box: Shape, opts: {
52
- placement?: {
62
+ placement: {
53
63
  side: BwlSide;
54
64
  offset?: number | null;
55
- } | null;
56
- from?: {
57
- x: number;
58
- y: number;
59
65
  };
60
66
  selfId?: string;
61
67
  }): {
62
68
  x: number;
63
69
  y: number;
64
70
  };
71
+ /**
72
+ * **B-168** — source 꼴 직접 끼우기 감지(하강 **전** 문서 기준). 키 = F id · 값 = 7단계까지 미룰 신규 유입(`A → X`) op.
73
+ * 조건 = X 로의 target 재배선이 없다 · X 가 F 의 옛 출발 A 에서 신규 유입을 받는다 · X 마다 첫 F 하나 · 그 신규 유입을
74
+ * 다른 op 가 참조하지 않는다(7단계 앞 단계 — 주석 연관 · 그룹 멤버 — 에서 아직 없다). 뒤 보정(`index.ts`)도 같은 판정을 쓴다.
75
+ */
76
+ export declare function sourceFormInsertsOf(ops: readonly SymbolicOp[], oldSourceOf: (flowId: string) => string | undefined): Map<string, Extract<SymbolicOp, {
77
+ kind: 'sequenceFlow.add';
78
+ }>>;
79
+ /**
80
+ * **B-213** — 2단계 플로우 반복(탐욕 배치)에서 **합류를 놓지 못하게 미룰 출구** id. 다이아몬드 합류 M 으로 드는 출구
81
+ * s → M 중 **출발 s 가 M 의 다른 비루프 선행 q 에 닿고 q 가 s 보다 2 이상 깊은** 것이다 — 그 출구가 먼저 M 을 놓으면
82
+ * M 이 s 바로 다음 열에 서고 긴 갈래 끝 q 가 M 으로 거꾸로 든다(원격 반품 v1). 깊이 = 이 시퀀스들의 비루프 최장 경로
83
+ * (되돌아가는 간선 = DFS 스택 위로 가는 간선 · 뿌리 = 유입 없는 출발 → 배치 노드 → 나머지 출발 순). 선언 순서와 무관하다.
84
+ *
85
+ * 🔴 **좁힌 조건 둘 다 실측이 정했다**(원장 `B-213(N20)`) — ⓐ 깊이 차 1 은 미루지 않는다: 낙하(게이트웨이 열) ·
86
+ * `freeSpot` 비켜 놓기가 이미 받치는 자리인데 미루면 배치 경로가 바뀌어 fixture 3건이 나빠졌다(교차 66 → 73 등).
87
+ * ⓑ **s 가 q 에 닿을 때만** 미룬다: 갱신 배치에서 기존 노드는 깊이 0 으로 잡히는데 실제로는 오른쪽 멀리 있을 수 있어,
88
+ * 닿지 않는 기존 선행의 출구를 미루면 멀쩡하던 합류가 거꾸로 선다(합성 갱신 사례).
89
+ */
90
+ export declare function diamondShortcutExitsOf(flows: readonly Pick<IrSequenceFlow, 'id' | 'source' | 'target'>[], nodeIds: readonly string[], isBatchNode: (id: string) => boolean): Set<string>;
65
91
  /**
66
92
  * 하강 중에야 드러나는 거부를 **구조화된 issue 로** 올린다 — `applyBpmnOps` 가 자유 문장 `lower-failed` 대신
67
93
  * 이 issue 를 그대로 낸다(LLM 교정 루프가 읽을 수 있게). 롤백은 다른 throw 와 같다(undo 에피소드가 되감는다).
@@ -70,11 +96,43 @@ export declare class LowerIssueError extends Error {
70
96
  readonly issue: ResolveIssue;
71
97
  constructor(issue: ResolveIssue);
72
98
  }
99
+ /** B-119 ⓒ′ — 위 집합을 읽는다(주입한 `lower` 가 쓰지 않았으면 빈 집합). */
100
+ export declare function labelLandingFlowIdsOf(services: object): ReadonlySet<string>;
101
+ /** B-209 — 위 맵을 이 배치의 서비스 객체에 건다(`applyBpmnOps` 가 하강 직전에). */
102
+ export declare function provideLowerLocaleTexts(services: object, writes: ReadonlyMap<string, LocaleWrite>): void;
103
+ /**
104
+ * B-188 ⓑ — 이 배치의 7단계 행 이동(`node.update{band|lane}`)이 **떠난 자리**(이동 직전 bbox · id = 옮긴 노드). 그 자리를
105
+ * 감싸던 이어지지 않은 선의 ⊓ 를 `foldVacatedDetours` 가 접는다(관통 보정 1차 뒤). 넘기는 길은 B-119 ⓒ′ 와 같다.
106
+ */
107
+ export interface VacatedBox {
108
+ id: string;
109
+ x: number;
110
+ y: number;
111
+ width: number;
112
+ height: number;
113
+ }
114
+ /** B-188 ⓑ — 위 목록을 읽는다(주입한 `lower` 가 쓰지 않았으면 빈 목록). */
115
+ export declare function vacatedBoxesOf(services: object): readonly VacatedBox[];
116
+ /** B-189 — 위 목록을 읽는다(주입한 `lower` 가 쓰지 않았으면 빈 집합). */
117
+ export declare function ownEndpointRelaidIdsOf(services: object): ReadonlySet<string>;
118
+ /** B-196 — 위 목록을 읽는다(주입한 `lower` 가 쓰지 않았으면 빈 집합). */
119
+ export declare function leftPassedMessageIdsOf(services: object): ReadonlySet<string>;
73
120
  /** 검증 통과가 전제 — 참조 해소 실패는 여기서 프로그래밍 오류로 throw 되고 applyBpmnOps 가 롤백한다. */
74
121
  export declare function lowerOps(ops: readonly SymbolicOp[], services: ResolverServices): void;
75
122
  /** 주석 도형이 소유자로 등장할 수 있는 finding 의 소유 도형 id. */
76
123
  export declare function annotationOwnersOf(finding: GeometryFinding): readonly string[];
77
- export declare function repairIntroducedAnnotationOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeShapeIds: ReadonlySet<string>): void;
124
+ /**
125
+ * **B-219** — 이 배치가 **앵커 대비** 옮긴 주석(중심 2px 넘게 움직였고 앵커와 같은 델타로 함께 옮겨진 승객이 아닌 것).
126
+ * placement 이동 · 주석 보정(B-56) · 문구 변경의 상자 크기 변화가 여기 든다 — 그 주석이 지금 가진 판정은 이 배치가 만든
127
+ * 관계라 선재 키여도 응답에 싣는다. 앵커만 움직이고 주석이 제자리면 들지 않는다(그 주석의 관계는 그대로다).
128
+ * `anchorOf` = 연관선 반대 끝(없으면 앵커 비교 없이 이동만 본다). 앵커가 연결이면 가운데 waypoint 로 잰다.
129
+ */
130
+ export declare function repositionedAnnotationIdsOf(before: GeometryScene, after: GeometryScene, anchorOf: (annotationId: string) => string | undefined): Set<string>;
131
+ export declare function repairIntroducedAnnotationOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, beforeShapeIds: ReadonlySet<string>,
132
+ /** B-210 — 1차 관통 보정이 미룬 간선(B-142). 그 간선이 지나는 **새** 주석도 이 패스가 맡는다. */
133
+ deferredEdgeIds?: ReadonlySet<string>,
134
+ /** B-219 — 이 배치의 `annotation.update` 가 placement 로 둔 주석. 이것만 옮겨 본 자리의 선재 키 판정을 종전대로 거른다. */
135
+ placedAnnotationIds?: ReadonlySet<string>): void;
78
136
  /**
79
137
  * **B-91 예방** — 이 배치가 만든 **풀 × 풀 겹침**을, 오른쪽 이웃을 밀어 해소한다.
80
138
  *
@@ -97,13 +155,22 @@ export declare function repairIntroducedAnnotationOverlaps(services: Pick<Resolv
97
155
  */
98
156
  export declare function repairIntroducedPoolOverlaps(services: Pick<ResolverServices, 'elementRegistry' | 'modeling'>, beforeKeys: ReadonlySet<string>, sceneBefore: GeometryScene): void;
99
157
  /**
100
- * 🔴 **B-120 — 문서 전체가 한 우변에 맞춰져 있던 풀들을 배치 뒤에도 한 우변으로 되돌린다.**
158
+ * 🔴 **B-120 — 배치 전 한 우변에 맞춰져 있던 풀 무리를 배치 뒤에도 한 우변으로 되돌린다.**
101
159
  *
102
160
  * 증언(표본 86) = *"레인 끝부분이 걸린건 위쪽의 세개의 레인이 정렬되있어서야"*. 배치는 두 풀만 늘렸고 **늘리는 경로가 둘인데
103
161
  * 기준이 다르다**(`shiftGroups` = 내용 우단 +150 · `createSpace` = 밀기 폭) — 한 우변이 셋으로 갈렸다.
104
- * 🔴 **판별자는 「양」이 아니라 「대상」이다** — 우변이 같은 풀이 몇 개인가가 아니라 **문서 전체가 한 우변이었나**. N20 전이 =
105
- * 전체 정렬 무리는 사람이 **3/3 반응** · 부분 무리는 **0/5**. 부분 일치는 도구 문서에서도 우연히 생긴다(저장본 부분 무리 19/69 ↔
106
- * 전체 정렬 0/69). ⇒ 발동은 **배치 전 모든 풀(≥2)의 우변이 같을 때만**이다.
162
+ *
163
+ * 무리 = 배치 전 우변이 허용 오차 안에서 같은 풀들(크기 ≥2). 범위는 두 단계로 넓어졌다:
164
+ * - **ⓐ 문서 전체가 한 우변**(2026-09-28) — N20 전이 = 전체 정렬 무리는 사람이 **3/3 반응** · 부분 무리는 **0/5**.
165
+ * - **ⓓ 부분 무리 — 블랙박스 멤버가 없을 때만**(2026-10-06 · 표본 99) — 증언 = *"끝부분이 어긋난건 보였는데 … 맞추는게
166
+ * 맞아보이긴해"*. N20 = 사람 문서(풀 ≥2) 98 중 **부분 무리 문서 25** · ⓐ 뒤 현행이 깨는 무리는 **부분 무리뿐**(계보 7/23) ·
167
+ * 합성 재고(블랙박스 없는 부분 무리 1,899) = 깨짐 376 → 복원 **312** · 판정 키 · 교차 · 끝점 공유 교차 · 흐름 간선 변화 **0**.
168
+ * 🔴 **블랙박스 멤버가 있는 부분 무리는 뺀다** — 계보 반사실에서 블랙박스 풀이 **570 → 2155** 로 늘고 그 우변에 붙던 메시지가
169
+ * 다시 그려져 끝점 공유 교차가 **1 → 3** 이 됐다(그 회차의 사람은 맞추지 않았다). ⚠ 아래 가드 ②는 그 메시지를 못 막았다 —
170
+ * 이 패스 시점엔 우변에 안 붙어 있었고 뒤 라우팅이 붙인다(추정). ⚠ 판별은 **이 패스 시점**의 모델이다(배치가 빈 풀에 노드를
171
+ * 넣었으면 더는 블랙박스가 아니다).
172
+ * ⚠ 부분 일치는 도구 문서에서도 우연히 생긴다(저장본 19/69) — 그런 무리는 대개 처음부터 같은 폭이라 되돌려도 원래 모양이다
173
+ * (계보 102 재하강에서 기하가 바뀐 문서 = 표본 99 하나).
107
174
  *
108
175
  * **목표 우변 = max(base 우변, 각 풀의 산출 내용 우단 + 150)** — 사람 값이 있는 회차(표본 85 = 2338)를 **정확히** 낸 규칙은
109
176
  * 이것 하나였다(무리 최댓값 · 풀별 base 여백 보존은 둘 다 2473). base 우변 아래로는 **안 줄인다**.