문서와 심볼

aidlc-docs 를 그대로 읽는다 · 읽기 전용
/home/sunny/enode-fixup-workshop2-visualizer연결 중
inception/application-design/unit-of-work-dependency.md305 줄

유닛 의존 · 접점 · 파일 겹침

unit-of-work.md 가 유닛 하나씩을 적었다면 이 문서는 둘 사이를 적는다.
셋을 담는다 — 무엇이 무엇을 딛는가, 같은 파일을 누가 만지는가, 접점의 모양은
무엇인가.


1. 착수 순서

   U-바닥  ->  U-관측API  ->  U-대기열  ->  U-drain  ->  U-제어판
   CP0         CP1            CP2          CP3         CP4

                   └──────>  U-MCP                     장면 밖 · 병렬
                             CP5

U-MCP 는 이 사슬에 안 들어간다 (2026-09-05 에 범위로 들어왔다 —
requirements/decisions.md 5절). U-관측API 의 라우트 둘만 딛으므로 CP1 이
초록인 순간부터 언제든 착수 가능
하고, 파일 겹침이 0 이라 U-drain ·
U-제어판과 같은 시간에 돈다. 착수 순서에 자리를 안 잡는 유일한 유닛이다.

병합 순서가 곧 착수 순서다 (START.md §5). 배치 폭이 1 이라 직렬이고,
그래서 「두 유닛이 같은 파일을 동시에 만진다」가 이 실행에서는 **「먼저 들어간
쪽 위에 뒤가 얹힌다」**로 읽힌다. 병렬로 돌릴 때의 병목 목록이 아니라 얹는
순서의 목록
이다.

U-바닥이 늦으면 전원이 선다. 다른 넷이 전부 이것을 딛는다. CP0 가
초록이어야 다음이 시작된다.

1.1 상자로 본 것

같은 것을 상자로 그린 것이다. 화살표는 「딛는다」 방향으로 아래에서 위를
가리킨다
— 먼저 서야 하는 것이 아래에 있다.

    ┌──────────────────────────────────┐      ┌──────────────────────────────────┐
    │ U-MCP                CP5  ○      │      │ U-제어판             CP4  ○      │
    │ runctl mcp · stdio 하나          │      │ enodectl serve · 프로세스 제어   │
    │ 도구 일곱 · 새 의존 0            │      │ + 호스트 화면 S3 · S5            │
    └──────────────────────────────────┘      └──────────────────────────────────┘
                     ▲                                ▲                 ▲
                     │                                │                 │
                     ├────────────────────────────────┘                 │
                     │                                                  │
    ┌──────────────────────────────────┐      ┌──────────────────────────────────┐
    │ U-관측API            CP1  ●      │      │ U-drain              CP3  ○      │
    │ GET /v1/nodes · /v1/runs         │      │ 정책 파일 · 광고 왕복            │
    │ + 중앙 화면 다섯                 │      │ 두 모드 · 해제                   │
    └──────────────────────────────────┘      └──────────────────────────────────┘
                                                              ▲
                                                              │
                                              ┌──────────────────────────────────┐
                                              │ U-대기열             CP2  ●      │
                                              │ QUEUED · 202 · FIFO              │
                                              │ 승격 · 깨우기 다섯 자리          │
                                              └──────────────────────────────────┘

  ┌────────────────────────────────────────────────────────────────────────────┐
  │ U-바닥               CP0  ●    스키마 · 상태 어휘 · 조회 메서드 · 정책 자리 │
  │                                                                            │
  │ 위의 다섯이 전부 이것을 딛는다. 화살표 다섯을 안 그리는 이유다             │
  └────────────────────────────────────────────────────────────────────────────┘
   ●  초록 — 코드까지 끝나고 게이트가 통과했다        CP0 · CP1 · CP2
   ○  아직 안 돌았다                                CP3 · CP4 · CP5

읽는 법 셋.

① 바닥을 밑에 깐 것은 그림 사정이 아니다. 나머지 다섯이 전부 그것을
딛어서 화살표를 그리면 다섯 줄이 지나간다. 1절의 「U-바닥이 늦으면 전원이
선다」가 그림에서는 이 모양이다.

② 왼쪽 기둥이 비어 있다. U-관측API -> U-MCP 간선 하나뿐이고 그 위가
끝이다. CP1 이 초록이므로 U-MCP 는 지금 착수 가능하고 위로 아무도 안
매달려 있다
— 늦어도 아무것도 안 막는다.

③ 오른쪽 기둥이 직렬이다. U-대기열 -> U-drain -> U-제어판 이 한 줄로
서 있고 거기에 U-관측API -> U-제어판 이 가로질러 들어온다. 남은 병목은
그 세로줄 하나
이고, U-제어판 이 기다리는 것은 U-drain 의
policy.Read · policy.Write 다.

두 기둥이 만지는 파일이 하나도 안 겹친다(3절의 행렬). 그래서 같은 시간에
돌아도 서로 안 부딪친다.


2. 의존 행렬

세로가 딛는 쪽, 가로가 딛히는 쪽이다. X 는 실제로 코드가 걸리는 자리다.

                  바닥   관측API   대기열   drain   제어판   MCP
   U-바닥          —
   U-관측API       X       —
   U-대기열        X               —
   U-drain         X               X        —
   U-제어판        X       X                X       —
   U-MCP                   X                                 —

의존이 걸리는 자리 여섯. 각각 무엇을 받는지 적는다 — 이름만 적으면
받는 쪽이 모양을 짐작한다.

U-MCP 의 행에 X 가 하나뿐이고, 열은 통째로 비어 있다. 아무도 그것을
안 딛는다는 뜻이고, 그래서 이 유닛은 뒤에 오는 것을 막지 않는다.

딛는 쪽 딛히는 쪽 받는 것
U-관측API U-바닥 store.ListNodes · store.ListRuns · StepView.Chosen · contract.Policy · match.Reject 의 json 태그
U-대기열 U-바닥 store.QUEUED 상수 · store.ListQueued · store.DrainingNodes · queue.go 파일 자체
U-drain U-바닥 contract.Policy{Drain} · nodes 정책 열 · UpsertAdvert 의 이전 정책 반환
U-drain U-대기열 store.WakeQueued — 해제를 감지한 광고 처리가 부른다 (깨우기 여섯째)
U-제어판 U-관측API GET /v1/nodes · GET /v1/runs/{id} — 현재 작업과 마지막 관측을 조립한다
U-제어판 U-drain policy.Read · policy.Write — 자기 파서를 안 만든다
U-MCP U-관측API GET /v1/nodes · GET /v1/runs — fleet.list · runs.list 가 감싼다. HTTP 의존이고 패키지 임포트가 아니다

U-제어판 -> U-관측API 는 HTTP 의존이다. 패키지 임포트가 아니라 라우트를
부르는 것이고, 그것이 NFR-P1 · NFR-P2 가 요구하는 모양이다.


3. 파일 겹침 행렬

o 는 고친다, c 는 부르기만 한다, NEW 는 신설이다. 한 행에 o 가 둘
이상이면 그 파일이 접점이고 4절에 처리가 있다.

   파일                                          바닥  관측  대기열  drain  제어판  MCP
   ───────────────────────────────────────────── ───── ───── ────── ────── ────── ─────
   internal/store/schema.sql                      o
   internal/store/store.go                        o                   o
   internal/store/observe.go                      o
   internal/store/list.go                    NEW  o     c
   internal/store/queue.go                   NEW  o           o
   internal/store/release.go                                  o
   internal/store/reap.go                                     o       c
   internal/contract/advert.go                    o
   internal/match/match.go                        o
   internal/boundary/imports_test.go         NEW  o
   internal/api/api.go                                  o     o       o
   internal/api/list.go                      NEW        o
   internal/api/queue.go                     NEW              o
   internal/api/ui/                          NEW        o
   cmd/mediator/main.go                                       o
   internal/enode/policy.go                  NEW                      o      c
   internal/enode/advertise.go                                        o
   internal/enode/claim.go                                            o
   internal/panel/                           NEW                             o
   internal/panel/ui/                        NEW                             o
   cmd/enodectl/serve.go                     NEW                             o
   cmd/enodectl/main.go                                                       o
   cmd/enodectl/proc_unix.go                                                  o
   cmd/enodectl/proc_windows.go                                               o
   internal/mcp/                             NEW                                      o
   internal/runctl/client.go                                                           o
   cmd/runctl/main.go                                                                  o

마지막 셋이 U-MCP 열이다. 셋 다 o 가 하나뿐이라 4절에 처리가 없다 —
겹치는 자리가 없다는 것이 그 뜻이다.

internal/match/match.go 는 unit-boundaries.md §2 가 「아무 유닛도 안
만진다」로 적은 파일이다. requirements.md §8.4 ①-b 의 결정으로 U-바닥이
한 번 만진다
— Reject 에 json 태그 셋을 다는 것이고 Match 의 시그니처와
로직은 그대로다. NFR-I4 는 지켜진다. 그 문장을 여기서 정정한다.

internal/panel/ui/ 는 행렬에 새로 선 줄이다. U-화면을 U-제어판에 합쳤으므로
파일 경계는 유지하고 계획 단위만 합쳤다는 것을 행렬이 그대로 보인다
(aidlc-state.md 실행 계획 4절).


4. 겹치는 자리와 처리

행렬에서 o 가 둘 이상인 행은 넷이다.

internal/api/api.go — 셋이 만진다

가장 뜨거운 자리다. 기존 라우트 15개가 전부 이 파일을 지나고, 새 유닛
셋이 각각 다른 이유로 들어온다.

   U-관측API   등록 줄 셋 — /v1/nodes · /v1/runs · /ui/
               핸들러 본체는 list.go 와 ui/ 에 있다.  이 파일에는 줄만 는다

   U-대기열    submit 의 QUEUED 분기 (매칭 거절 처리)
               submit 이 BusyNodes 에 DrainingNodes 를 합쳐 넘기는 자리
               postResult · postCancel · postNodes 재기동 감지 뒤의 WakeQueued
               등록 줄 없음 — 새 라우트를 안 만든다

   U-drain     광고 응답에 policy 를 더하는 줄 하나
               postResult 끝의 at-boundary 종료 블록 (store.Cancel)
               해제를 감지한 광고 처리의 WakeQueued

처리 — 병합 순서대로 직렬로 받는다. 관측이 먼저 등록 줄을 넣고, 대기열이
submit 과 종료 지점들을 고치고, drain 이 광고 응답과 postResult 끝에
얹는다. 셋이 같은 함수를 만지는 자리는 postResult 하나뿐이고 거기서도
대기열은 끝의 깨우기 · drain 은 끝의 종료 블록
이라 줄이 겹치지 않는다.

위험 판정 — submit 과 postResult 는 모든 제출과 모든 단계 보고가
지나는 뜨거운 경로
다. 되돌리기는 이 파일의 등록 줄과 두 블록만 되돌리면
된다 (aidlc-state.md 실행 계획 2절).

internal/store/store.go — 둘이 만진다

   U-바닥     UpsertAdvert 가 정책을 받는 시그니처까지 만들고 이전 정책을 돌려준다
   U-drain    그 자리에 값을 채우기만 한다

처리 — 순서가 곧 처리다. 바닥이 모양을 먼저 세우므로 drain 은 시그니처를
안 바꾼다.

internal/store/queue.go — 둘이 만진다

   U-바닥     파일을 만들고 QUEUED 상수 · DrainingNodes · ListQueued 를 넣는다
   U-대기열   같은 파일에 WakeQueued 를 더한다

처리 — 같은 파일이지만 함수가 다르다. 겹치는 줄이 없다.

internal/store/reap.go — 하나가 고치고 하나가 부른다

   U-대기열   Reap 뒤의 WakeQueued 호출을 더한다.  파일을 만진다
   U-drain    at-boundary 종료 경로로 Cancel 을 호출만 하고 안 고친다

처리 — c 는 겹침이 아니다. Cancel 이 이미 verdict 를 쓰고
sealRecord 까지 부르므로 drain 이 고칠 것이 없다 (requirements.md §2.1).

겹치지 않는데 적어 두는 자리 하나

internal/match/match.go 는 U-바닥 하나만 만진다. 그런데 match.Match 를
아무도 안 만진다는 것이 NFR-I4 이자 ADR-014 결정 3 이라
여기 적는다 —
태그를 다는 것과 매처를 고치는 것을 뒤에 오는 사람이 헷갈리지 않게.


5. 접점 표

requirements.md §4.1 의 확정된 계약을 유닛 쌍에 붙인 것이다. §4.1 이
값의 정본이고 여기는 소유자를 적는다. Functional Design 이 내부를 다듬을
수 있으나 아래 모양은 못 바꾼다
— 유닛 둘 이상이 읽기 때문이다.

# 접점 내는 유닛 쓰는 유닛 모양
1 contract.Policy{Drain} U-바닥 U-drain · U-관측API · U-제어판 "" | "graceful" | "at-boundary". 문자열 그대로
2 store.DrainingNodes(ctx) U-바닥 U-대기열 · U-drain map[string]bool. match.Match 의 busy 와 같은 모양
3 store.ListNodes · store.ListRuns U-바닥 U-관측API decisions.md 2절의 응답 모양
4 store.UpsertAdvert 의 반환 U-바닥 U-drain 이전 정책을 돌려준다. 해제 감지가 이 값을 읽는다
5 match.Reject 의 json 태그 U-바닥 U-관측API {code, as, reason}. 두 라우트가 같은 글자를 쓴다
6 store.WakeQueued(ctx) error U-대기열 U-drain · 기동 도착 순서로 전부. 재진입 · 동시 호출 안전
7 GET /v1/nodes 노드 항목 U-관측API U-제어판 · 화면 ADR-065 모양 + draining
8 GET /v1/runs 의 행 U-관측API 화면 decisions.md 2절 + current[] + reject
9 current[] 의 항목 U-관측API 화면 (S1 카드 · Run 행) {node, step, attempt, started_at}. CLAIMED 마다 하나
10 광고 응답의 policy U-drain 데몬 (Worker) advertResponse 에 policy. GET /v1/nodes 의 납작한 draining 과 이름이 다르다 (§4.2)
11 policy.Read · policy.Write U-drain U-제어판 정본은 internal/enode. 제어판은 파서를 안 만든다
12 enodectl serve 의 라우트 U-제어판 화면 (S3 · S5) §4.3. 전부 ?node=<name> 을 받는다
13 GET /api/status 의 observed · stale U-제어판 화면 (S5) 캐시가 비면 observed 없음 · stale true

주인 없는 접점 0. 13 개가 전부 내는 유닛 하나를 갖는다.

접점 하나에 대한 주의 — 10 번

광고 응답의 policy 와 GET /v1/nodes 의 draining 은 같은 값을 다른
이름으로 낸다.
헷갈리는 자리라 근거를 다시 적는다.

   광고 응답        policy{drain}     요청이 올려보낸 것과 같은 객체의 왕복이다
   GET /v1/nodes    draining          관측이고 ADR-065 가 납작한 필드로 짰다

데몬은 policy.drain 만 읽는다. 왕복이 필요한 이유는 Worker 가 Mediator
가 확인한 값
으로 움직여야 하기 때문이다 — 그렇지 않으면 노드는 임대를
놓았는데 중앙은 아직 후보로 아는 창이 생긴다.


6. 유닛 사이를 안 건드리는 것

의존이 없다는 것도 값이다. 뒤에 오는 사람이 새로 잇지 않게 적는다.

   U-관측API  ->  U-대기열      없다.  QUEUED 는 그냥 또 하나의 state 문자열이다
   U-관측API  ->  U-drain       없다.  draining 은 nodes 열의 투영이다
   U-대기열   ->  U-drain       없다.  거꾸로만 있다 (drain 해제가 WakeQueued 를 부른다)
   U-제어판   ->  U-대기열      없다.  제어판은 대기열을 안 본다
   internal/panel   ->  internal/store    금지.  경계 검사가 센다
   internal/panel   ->  internal/api      금지
   internal/api/ui  ->  internal/store    금지
   internal/enode   ->  internal/panel    금지.  데몬은 제어판을 모른다

아래 넷은 U-바닥이 내는 경계 검사 테스트가 go/parser 로 기계로 센다
(NFR-P5 · unit-boundaries.md §5). 패키지 이름이 바뀌면 이 표도 같이 바꾼다 —
검사기는 표를 읽는다.