# Interface Map JSON 생성 가이드

이 문서는 저장소를 분석해 Interface Map에서 바로 가져올 수 있는 JSON을 만드는 사람과 에이전트를 위한 현재 기준이다. 기준 구현은 `client/scripts/map-editor.js`, `shared/document-model.js`, `shared/contract-core.js`, `shared/object-icons.js`이며, 문서와 구현이 다르면 구현을 우선한다.

## 1. 생성 원칙

- 모든 파일을 노드로 만들지 않는다. 독립된 책임, 배포 단위, 데이터 경계, 외부 연동처럼 협업에서 의미 있는 모듈만 객체로 만든다.
- 폴더는 기술 이름이 아니라 탐색에 유용한 경계로 묶는다. 보통 클라이언트, 서버, 데이터·외부 서비스, 운영·검증 정도가 적절하다.
- 화살표는 실제 의존성 또는 런타임 통신만 나타낸다. 같은 폴더에 있다는 이유만으로 연결하지 않는다.
- 이름은 사람이 이해하는 역할, `codeLocation`은 실제 파일 또는 디렉터리 경로로 적는다.
- 저장소에서 확인되지 않은 관계는 단정하지 않는다. 필요하면 `custom` 연결에 "선택형", "예정"처럼 성격을 명시한다.
- 담당자 ID는 워크스페이스마다 다르다. 범용 가져오기 파일은 `members: []`, 노드의 `owners: []`로 만들고 가져온 뒤 지정한다.
- 비밀값, 사설 주소, 운영 토큰, 실제 사용자 데이터는 넣지 않는다.

## 2. 최상위 형식

현재 가져오기 형식은 `ifmap` 버전 2다.

파일 이름은 일반적인 `프로젝트명.json` 형식을 사용한다. `format: "ifmap"`은 JSON 내부에서 판별하므로 `.ifmap.json` 같은 복합 확장자를 사용하지 않는다.

```json
{
  "format": "ifmap",
  "version": 2,
  "title": "프로젝트 이름 · 구조",
  "members": [],
  "nodes": [],
  "edges": []
}
```

- `nodes`와 `edges`는 배열이어야 한다.
- 모든 `id`는 1~128자의 영문, 숫자, `.`, `_`, `:`, `-`만 사용한다.
- `__proto__`, `prototype`, `constructor`는 ID로 사용할 수 없다.
- 같은 배열 안에서 ID를 중복해서는 안 된다.
- `edge.source`, `edge.target`, `node.parent`는 반드시 존재하는 노드 ID를 가리켜야 한다.
- `version`이 앱이 지원하는 버전보다 높으면 가져올 수 없다.

### 2.1 구성요소 가져오기 형식

현재 맵을 교체하지 않고 객체 하나 또는 폴더 트리를 추가하려면 빈 캔버스를 우클릭해 `구성요소 가져오기…`를 사용한다. 이때는 `ifmap-component` 버전 1 형식을 사용한다.

```json
{
  "format": "ifmap-component",
  "version": 1,
  "nodes": [
    { "id": "folder-chat", "kind": "folder", "parent": null, "name": "채팅 기능", "x": 0, "y": 0, "w": 520, "h": 240 },
    { "id": "object-client", "kind": "object", "parent": "folder-chat", "name": "채팅 클라이언트", "x": 20, "y": 20, "w": 200, "h": 100 },
    { "id": "object-server", "kind": "object", "parent": "folder-chat", "name": "채팅 서버", "x": 280, "y": 20, "w": 200, "h": 100 }
  ],
  "edges": [
    {
      "id": "edge-chat",
      "source": "object-client",
      "target": "object-server",
      "protocol": "websocket",
      "interaction": "bidirectional-stream",
      "anchors": { "source": "right", "target": "left" },
      "spec": { "url": "/ws/chat" }
    }
  ]
}
```

- 최상위 노드는 정확히 하나이며 객체 또는 폴더여야 한다.
- 객체 하나를 가져올 때는 `nodes`에 그 객체 하나만 넣고 `edges`는 비운다.
- 폴더 구성요소에는 중첩 폴더, 객체, 이벤트와 이들 사이의 내부 연결을 넣을 수 있다.
- 모든 `parent`, `source`, `target` 참조는 구성요소 내부 ID를 가리켜야 한다.
- 가져올 때 모든 노드·연결 ID를 새로 발급하므로 기존 맵을 덮어쓰지 않고 같은 구성요소를 여러 번 추가할 수 있다.
- 하위 요소의 상대 배치는 유지하며 폴더가 모든 하위 요소를 감싸도록 필요하면 크기를 확장한다.
- 우클릭 지점을 중심으로 구성요소 전체가 들어가는 가장 가까운 20px 격자 위치를 찾아 배치한다.
- 담당자는 현재 워크스페이스 구성원과 일치할 때만 연결하며, 연결되지 않은 이름은 이전 담당자로 보존한다.

## 3. 노드

공통 권장 필드는 다음과 같다.

```json
{
  "id": "object-auth-service",
  "kind": "object",
  "parent": "folder-server",
  "name": "인증 서비스",
  "x": 40,
  "y": 60,
  "w": 200,
  "h": 100
}
```

좌표는 최상위 노드는 맵 좌표, 폴더 안 노드는 부모 폴더의 콘텐츠 영역 기준 상대 좌표다. 기본 격자는 20px이므로 좌표와 크기를 20의 배수로 두는 것을 권장한다.

### 3.1 객체 `object`

```json
{
  "id": "object-api",
  "kind": "object",
  "parent": "folder-server",
  "name": "Fastify 애플리케이션",
  "codeLocation": "server/server.js",
  "icon": "nodejs",
  "accentColor": "#10b981",
  "description": "HTTP API와 정적 페이지를 구성하고 권한을 집행합니다.",
  "status": "done",
  "owners": [],
  "x": 40,
  "y": 60,
  "w": 200,
  "h": 100
}
```

객체 크기는 가로 200px(격자 10칸), 세로 100px(격자 5칸)로 고정된다. 가져오기 JSON이나 기존 문서에 다른 `w`, `h`가 있어도 클라이언트와 서버가 고정 크기로 정규화한다. 폴더와 메모만 화면에서 크기를 조절할 수 있다.

- `status`: `todo`, `wip`, `done`, `blocked`
- `accentColor`: `#9ca3af`, `#3b82f6`, `#06b6d4`, `#10b981`, `#f59e0b`, `#f97316`, `#ef4444`, `#8b5cf6`, `#ec4899`
- 주요 `icon`: `document`, `folder`, `module`, `javascript`, `typescript`, `python`, `html`, `css`, `nodejs`, `json`, `yaml`, `markdown`, `shell`, `docker`, `nginx`, `database`, `http`, `github-actions`, `api-blueprint`
- 그 밖의 허용 아이콘은 `shared/object-icons.js`의 `OBJECT_ICONS`를 확인한다.
- `description`, `progressNote`, `deadline`은 선택 사항이다. `deadline`은 `YYYY-MM-DD` 형식만 사용한다.

### 3.2 폴더 `folder`

```json
{
  "id": "folder-server",
  "kind": "folder",
  "parent": null,
  "name": "서버",
  "icon": "server",
  "description": "인증·권한·데이터·실시간 협업 경계",
  "collapsed": false,
  "x": 100,
  "y": -700,
  "w": 820,
  "h": 660
}
```

허용 폴더 아이콘은 `folder`, `code`, `web`, `api`, `server`, `database`, `docker`, `hardware`다. 자식 노드 전체가 폴더 안에 들어오도록 크기를 잡는다. 새 폴더에는 `status`, `owners`, `deadline`을 넣지 않는다. 기존 JSON의 해당 값은 가져올 수 있지만 호환 데이터로만 보존되고 화면이나 작업 필터에는 사용되지 않는다.

### 3.3 이벤트 `event`

외부 입력이나 시간 흐름을 별도 요소로 보여줄 가치가 있을 때만 사용한다.

```json
{
  "id": "event-push",
  "kind": "event",
  "parent": null,
  "name": "main 브랜치 push",
  "x": 0,
  "y": 0,
  "w": 200,
  "h": 60
}
```

이벤트는 이름 중심의 단순 노드다. 과거의 `trigger`, `state`, `owners` 같은 메타데이터를 새 JSON에 추가하지 않는다.

### 3.4 메모 `memo`

```json
{
  "id": "memo-boundaries",
  "kind": "memo",
  "parent": null,
  "name": "설계 원칙",
  "markdown": "## 테넌트 경계\n모든 권한은 **서버에서** 검사합니다.",
  "x": 900,
  "y": 200,
  "w": 440,
  "h": 240
}
```

- 메모는 항상 `parent: null`이다.
- 본문은 `markdown` 문자열 한 필드만 사용한다. 과거의 `richText`, `text`, `blocks` 형식은 새 JSON에 사용하지 않는다.
- 제목, 문단, 목록, 체크 목록, 인용, 코드 블록, 링크, 굵게, 기울임, 취소선을 Markdown 문법으로 표현할 수 있다.
- 원시 HTML은 실행되지 않고 문자 그대로 표시되며, 링크는 `http`, `https`, `mailto`, 루트 상대 경로와 문서 앵커만 허용된다.
- 메모는 연결 대상과 경로 탐색 장애물로 사용되지 않는다.

## 4. 연결

모든 연결은 프로토콜에 맞는 `interaction`을 사용해야 한다.

| protocol | 의미 | 허용 interaction |
| --- | --- | --- |
| `call` | 모듈·함수 호출 | `one-way`, `request-response` |
| `http` | HTTP 요청 | `request-response` |
| `websocket` | WebSocket | `one-way`, `request-response`, `bidirectional-stream`, `publish-subscribe` |
| `event` | 이벤트·큐 | `one-way`, `publish-subscribe` |
| `db` | 데이터 저장소 접근 | `one-way`, `request-response` |
| `custom` | 그 밖의 관계 | 모든 interaction |

공통 권장 구조:

```json
{
  "id": "edge-client-api",
  "source": "object-client-store",
  "target": "object-api",
  "protocol": "http",
  "interaction": "request-response",
  "label": "문서 조회",
  "spec": {},
  "contractVersion": "1.0.0",
  "lifecycle": "active",
  "breaking": false,
  "changeNote": "",
  "reviewNote": "",
  "policy": {
    "timeoutMs": "5000",
    "retries": "0",
    "idempotency": "",
    "auth": "session-cookie + CSRF",
    "rateLimit": "",
    "dataClass": "internal",
    "traceHeader": ""
  },
  "anchors": { "source": "auto", "target": "auto" }
}
```

- `contractVersion`은 `1.0.0` 같은 SemVer 형태를 권장한다.
- `lifecycle`: `draft`, `review`, `approved`, `active`, `deprecated`, `retired`
- `dataClass`: `public`, `internal`, `confidential`, `personal`, `sensitive`
- `timeoutMs`는 양의 밀리초, `retries`는 0 이상의 정수 문자열로 적는다.
- 연결 담당자는 양 끝 객체의 담당자로 해석하므로 새 JSON에 별도 `owners`를 넣지 않는 것을 권장한다.

### 4.1 모듈 호출 `call`

```json
{
  "protocol": "call",
  "interaction": "request-response",
  "spec": {
    "contractLanguage": "javascript",
    "signature": "loadDocument(workspaceId)",
    "returnVariable": "document: Promise<InterfaceMapDocument>",
    "purpose": "현재 워크스페이스 문서를 읽습니다.",
    "paramsDescription": "workspaceId: 워크스페이스 ID",
    "returnDescription": "document: 정규화된 맵 문서"
  }
}
```

- 항상 보이는 핵심 입력은 `signature`와, 반환이 있을 때의 `returnVariable`이다.
- 상세 토글에는 `purpose`, `paramsDescription`, 반환이 있을 때의 `returnDescription`이 들어간다.
- `one-way`로 바꾸면 `returnVariable`, `returnDescription`을 넣지 않는다.

### 4.2 HTTP `http`

`spec`의 주요 필드는 `contractLanguage`, `method`, `path`, `status`, `contentType`, `request`, `response`, `parameters`, `headers`, `requestSchema`, `responseSchema`, `errorSchema`다.

- `method`는 유효한 HTTP 메서드여야 한다.
- `path`는 `/`로 시작해야 한다.
- `status`, `response`, `responseSchema` 중 하나 이상을 적어 응답 계약을 표현한다.
- `policy.auth`를 적는다. 공개 API면 `public`이라고 명시한다.

### 4.3 WebSocket `websocket`

`spec`의 주요 필드는 `contractLanguage`, `url`, `subprotocol`, `c2s`, `s2c`, `messageSchema`다. `url`은 필수다. `policy.auth`도 적는다.

### 4.4 이벤트·큐 `event`

`spec`의 주요 필드는 `contractLanguage`, `topic`, `delivery`, `ordering`, `partitionKey`, `retention`, `payload`, `payloadSchema`, `headers`, `deadLetter`다. `topic`은 필수이며 `payload` 또는 `payloadSchema`, `delivery`를 함께 적는 것을 권장한다.

### 4.5 DB 접근 `db`

`spec`의 주요 필드는 `contractLanguage`, `table`, `operation`, `transaction`, `consistency`, `query`, `paramsSchema`, 반환이 있을 때의 `resultSchema`다. `operation`은 필수이며 `table` 또는 `query`를 적는다.

### 4.6 사용자 정의 `custom`

`spec`의 주요 필드는 `contractLanguage`, `contractType`, `text`, `requestSchema`, 반환이 있을 때의 `responseSchema`다. 단순히 "의존함"이라고 쓰기보다 빌드, 정적 제공, 배포, 파일 보존처럼 관계의 성격을 적는다.

## 5. 배치 권장안

- 최상위 폴더 사이에는 최소 200px 이상의 여백을 둔다.
- 200×100 객체는 폴더 안에서 `x: 40, 320, 600`, `y: 80, 260, 440, 620`처럼 20px 격자에 맞춘 간격으로 배치하면 읽기 쉽다.
- 데이터 흐름은 가능하면 왼쪽에서 오른쪽으로 흐르게 한다.
- 교차 연결이 많은 공통 모듈은 중앙에 둔다.
- 메모는 모듈과 겹치지 않는 별도 공간에 둔다.
- 연결의 `anchors`는 특별한 이유가 없으면 양쪽 모두 `auto`로 둔다. 경로 좌표를 JSON에 저장하지 않는다.
- 연결선은 장애물을 피하면서 꺾임 수를 먼저 최소화하고, 꺾임 수가 같으면 가장 짧은 경로를 선택한다. 중간 꺾임과 긴 수평·수직 구간은 앱이 10px 단위, 즉 20px 캔버스 격자의 반 칸에 맞춰 자동 계산한다. 객체 면 가운데점이 반격자선 사이에 있으면 끝점에서 경로로 합류하는 짧은 어댑터 구간만 10px 단위 밖에 있을 수 있다.
- 연결선은 각 객체 접속점에서 50px 동안 선택한 면에 수직으로 직진한다. 화살촉 18px와 둥근 모서리 8px가 차지하는 길이를 제외해도 화살표 뒤에 캔버스 격자 한 칸(20px) 이상의 직선이 보인다. 경로가 이 종단을 안쪽에서 되짚지 않도록 필요하면 객체 바깥으로 한 번 더 우회하며, 새로 만들거나 이동하는 같은 레벨 요소 사이의 최소 간격은 기존처럼 20px다.
- 시작·도착 객체도 경로 장애물로 취급한다. 연결선은 선택한 상·하·좌·우 면의 종단 직선으로만 객체 경계에 접근하며, 다른 면에서 객체 내부를 통과해 화살촉만 떨어져 보이는 경로를 만들지 않는다.

## 6. 생성 절차

1. 저장소의 실행 진입점, 주요 디렉터리, 데이터 저장소, 외부 연동, 배포·테스트 파일을 확인한다.
2. 파일 목록이 아니라 책임 목록을 만든다.
3. 책임을 3~6개의 최상위 폴더로 묶는다.
4. 객체마다 사람이 읽을 제목, 실제 `codeLocation`, 역할 설명을 작성한다.
5. import·호출·HTTP·WebSocket·DB·배포 관계를 실제 코드 근거로 연결한다.
6. 프로토콜과 통신 형태가 위 표에 맞는지 확인한다.
7. 모든 부모, source, target 참조와 ID 중복을 검사한다.
8. JSON 파싱과 `shared/document-model.js`의 무결성 검사를 통과시킨다.
9. `npm run check`로 전체 회귀 검사를 실행한다.

## 7. 가져오기 주의사항

- 가져오기는 현재 맵 전체를 교체하므로 필요한 기존 맵을 먼저 내보낸다.
- 서버 관리자 또는 해당 워크스페이스 관리자만 가져올 수 있다.
- 다른 워크스페이스에서 내보낸 담당자는 로그인 아이디, 이어서 유일한 표시 이름으로 재연결된다. 범용 템플릿에서는 담당자를 비워 두는 편이 안전하다.
- 가져온 뒤 앱의 계약 검증에서 오류가 0개인지 확인한다. 경고는 의도한 생략인지 검토한다.
