문서와 심볼

aidlc-docs 를 그대로 읽는다 · 읽기 전용
/home/sunny/enode-fixup-workshop2-visualizer연결 중
inception/reverse-engineering/api-documentation.md234 줄

enode — API 표면

f46e187 시점의 구현이 실제로 서빙 · 노출하는 표면 전수. 정본
(mediator-api.md)과의 대조는 마지막 절에 관측으로만 적었다.

Mediator HTTP 표면 — 15개 라우트

라우트 정의가 흩어져 있지 않다. 15개 전부가 internal/api/api.go:53 Handler()
안의 단일 http.ServeMux 에 등록된다(:55-69). 라우터 라이브러리 의존 0 —
표준 net/http 의 Go 1.22+ 메서드+경로 패턴만 쓴다.

runctl → Mediator (제출 · 조회 · 취소)

메서드 · 경로 등록 핸들러
POST /v1/runs api.go:58 postRuns api.go:318 → submit api.go:325
POST /v1/runs/dry-run api.go:59 postDryRun api.go:323 → 같은 submit
GET /v1/runs/{id} api.go:60 getRun api.go:454
GET /v1/runs/{id}/record api.go:65 getRecord api.go:787
POST /v1/runs/{id}/cancel api.go:66 postCancel api.go:595

POST /v1/runs 와 POST /v1/runs/dry-run 은 같은 파서와 같은 매처를 쓴다 —
둘 다 submit(w, r, dry) 를 부른다. dry-run 이 실제 제출과 다른 판정을 내리지
않게 하는 배치다.

GET /v1/runs/{id} 는 steps 를 함께 준다(ADR-025, api.go:120-138 runView
· :141 view → internal/store/observe.go:45 Steps).

GET /v1/runs/{id}/record 는 tar 를 스트리밍한다. 봉인 전이면 409.

되묻기 (ADR-032)

메서드 · 경로 등록 핸들러
GET /v1/asks api.go:62 getAsks api.go:473
POST /v1/runs/{run}/steps/{seq}/answer api.go:63 postAnswer api.go:497

인박스(GET /v1/asks)가 정본이고 웹훅 푸시는 보조다.

enode → Mediator (당기기)

메서드 · 경로 등록 핸들러
POST /v1/nodes api.go:55 postNodes api.go:167
POST /v1/nodes/{id}/claim api.go:56 postClaim api.go:226
POST /v1/runs/{run}/steps/{seq}/result api.go:57 postResult api.go:267
PUT /v1/runs/{run}/steps/{seq}/log api.go:67 putLog api.go:618
PUT /v1/runs/{run}/steps/{seq}/blob/{name} api.go:68 putBlob api.go:668
GET /v1/runs/{run}/blob/{name} api.go:69 getBlob api.go:754

주의할 점 넷:

  • 별도의 하트비트 표면이 없다. POST /v1/nodes 광고가 곧 하트비트이고,
    응답이 유효 임대 목록 전체(leases)와 renew_seconds 를 나른다
    (ADR-016 · ADR-028, api.go:160-166 advertResponse · :207-216)
  • 표면에서 비멱등인 곳은 claim 하나뿐이다. postClaim →
    internal/store/claim.go:246 ClaimStep
  • claim 은 롱폴이다. 1초 티커로 최대 7,200초 대기하고, 줄 단계가 없으면
    204 No Content. 서버가 WriteTimeout 을 걸지 않고
    (cmd/mediator/main.go:114-117) 클라이언트도 타임아웃 없는 &http.Client{}
    를 쓴다(cmd/enode/main.go:132-136)
  • blob 경로가 비대칭이다. 올릴 때는 단계를 두 세그먼트로 가리키고
    (/runs/{run}/steps/{seq}/blob/{name}), 내릴 때는 Run 단위로 가리킨다
    (/runs/{run}/blob/{name}). 이유가 api.go:653-666 주석에 있다

PUT blob 은 계약이 그 단계에 스키마를 걸어 두었으면
internal/schema 로 검증하고, 실패하면 422 이고 저장하지 않는다
(api.go:717-731, schemaFor :777).

어휘 · 관측

메서드 · 경로 등록 핸들러
GET /v1/capabilities api.go:61 getCapabilities api.go:580
GET /v1/runs/{id}/ledger api.go:64 getLedger api.go:558

GET /v1/capabilities 는 존재만 답하고 여유는 답하지 않는다.
internal/store/store.go:392 Capabilities 주석이 "함대 전체의 합집합" 이라는
한계를 적고 여유 판단은 dry-run 으로 넘긴다.

GET /v1/runs/{id}/ledger 는 목록이지 본문이 아니다.

인증과 식별

15개 라우트 전부가 s.auth(...) 로 감싸여 있다. 예외 경로 0개 —
헬스체크도 메트릭도 인증 밖에 있지 않다.

  • 인증: Authorization: Bearer <token> 을 crypto/subtle 상수시간 비교
    (api.go:83 auth)
  • 식별: X-Enode-Principal 헤더. 검증하지 않는다(api.go:97 principal).
    인증과 식별을 일부러 분리했다

오류 코드 체계

api.go:110 fail 이 단일 출구다.

코드 뜻 발원지
400 계약 문법 위반 internal/contract/contract.go:954 Validate
401 토큰 불일치 api.go:83 auth
404 Run · 단계 · blob 없음 각 핸들러
409 전부 사용 중 (일시) · 봉인 전 record 요청 internal/match/match.go:24 CodeAllBusy · api.go:795-798
410 봉인 뒤의 쓰기 api.go:639-642 (putLog) · :686-689 (putBlob)
413 blob 크기 초과 internal/config MaxBlobBytes 기본 10 MiB
422 후보 없음 (영구) · 스키마 위반 internal/match/match.go:23 CodeNoCandidate · api.go:717-731
503 Record 저장소 미구성 api.go:45-51 needRecords

409 와 422 의 구분이 이 표면의 의미론적 핵심이다. 422 는 영구 거절(그런 노드가
함대에 아예 없다), 409 는 일시 거절(있는데 지금 다 바쁘다)이다. 클라이언트가
재시도해야 하는지가 여기서 갈린다.

CLI 표면 — runctl (8 하위명령)

cmd/runctl/main.go:124-310 디스패치, usage() 는 :402.

하위명령 하는 일 부르는 라우트
submit 계약 제출 POST /v1/runs
dry-run 배정만 계산 POST /v1/runs/dry-run
status Run 과 단계 상태 GET /v1/runs/{id}
record Record tar 내려받기 GET /v1/runs/{id}/record
cancel Run 취소 POST /v1/runs/{id}/cancel
capabilities 함대 어휘 GET /v1/capabilities
asks 미답 질문 목록 GET /v1/asks
answer 질문에 답 POST /v1/runs/{run}/steps/{seq}/answer

종료코드 4단 (cmd/runctl/main.go 상단 상수):

   0   성공
   1   Run 실패 (요청은 정상 처리됐고 Run 이 FAILED)
   2   요청 거절 (4xx)
   3   Mediator 도달 불가

permute() 가 플래그를 앞으로 모아 runctl submit x.json --wait 같은 자연스러운
순서를 받는다.

CLI 표면 — enodectl (8)

cmd/enodectl/main.go:44-70. HTTP 를 부르지 않는다 — 한 기계의 enode 프로세스만
관리한다. 셸 스크립트판이 node_id 를 다시 계산하던 것을 없애려고 Go 로 옮겼다.

   setup    list    id    start    stop    logs    status    --version

프로세스 조작의 OS 분기는 proc_unix.go / proc_windows.go 쌍이 맡는다.

CLI 표면 — enode

cmd/enode/main.go:29-46.

  • 하위명령: hook stop (R6 의 종료 훅이 곧 enode 자기 자신이다) · setup ·
    --version
  • 그 외에는 플래그 기반 상주 실행:
    --config · --mediator · --token · --every · --debug · --once ·
    --ready-file

--once 와 --ready-file 이 탄력 노드(단발 기동)용이다.
hook stop 은 어떤 실패에도 종료코드 0 을 돌려준다
(cmd/enode/hook.go:53) — 훅이 하네스 종료를 막지 않게 하려는 것이다.

아웃바운드 — asks 웹훅

Mediator 가 바깥으로 거는 유일한 호출이다.

  • 위치: internal/store/ask.go:102 PushAsks
  • 대상: 설정된 notify.asks_url
  • 형태: AskEvent JSON 하나를 POST
  • 재시도 없다. go func() 으로 쏘고 결과를 기다리지 않는다. 프로세스가
    즉시 죽으면 유실된다 — 인박스(GET /v1/asks)가 정본이고 푸시는 보조라는
    설계에서 허용된다

클라이언트 측 사용 현황

15개 라우트 전부에 최소 하나의 1차 클라이언트가 있다.

클라이언트 부르는 라우트 수 위치
internal/runctl/client.go 8 :118,177,187,197 외
internal/enode/{claim,advertise}.go 6 advertise.go:57 · claim.go:114,143,170,198,242
cmd/iapadapter/mediator.go 7 어댑터 자체 HTTP

한 가지 비대칭: GET /v1/runs/{id}/ledger 는 cmd/iapadapter/mediator.go:271
만 부르고 runctl 에는 대응 하위명령이 없다.

관측 — 문서의 표면 개수와 구현의 표면 개수

판정이 아니라 관측이다. 아래를 근거로 다음 스테이지가 판단한다.

구현이 서빙하는 것: 15

internal/api/api.go:55-69. 전수 확인이 grep 하나로 끝난다.

문서를 메서드+경로로 grep 하면: 21

mediator-api.md 를
grep -oE '(GET|POST|PUT|DELETE|PATCH) +/v1[A-Za-z0-9/{}_.-]*' | sort -u 로
세면 서로 다른 문자열 21개가 나온다(백틱 안쪽만 세면 16 — 패턴에 따라 수가
달라진다). 그 21개의 구성:

수 무엇 근거
15 구현이 서빙하는 라우트와 1:1 대응 —
2 ADR-021 이전의 한 세그먼트 표기가 § 제목에 남은 것: POST /v1/steps/{id}/result, PUT /v1/steps/{id}/log 제목 line 385 · 418, 대응표 line 563 · 564. 같은 문서 line 238-241 의 목록은 두 세그먼트이고 구현도 두 세그먼트다
2 자리표시자 이름만 다른 변형: {id} 대 {run}(blob 라우트), 그리고 § 제목이 GET/PUT 을 한 줄로 묶으면서 생긴 표기 line 193 · 432 · 565 대 line 239 · 240
1 §7 "없는 것 — 일부러" 에서 명시 기각: GET /v1/nodes?free=… line 672
1 "자리만 잡는다" 라고 적은 예약 자리: GET /v1/runs?state=&since= line 529

15 + 2 + 2 + 1 + 1 = 21.

따라서: 기각 · 예약 · 표기중복을 뺀 문서의 실제 표면 집합은 구현이 서빙하는
15개와 정확히 같다.

별개의 관측: 세 문장이 세 가지 수를 든다

정본 안에서 표면 개수를 말하는 문장 셋이 서로 다르다.

   system.md §5          "Mediator 표면 12개 전부"
   mediator-api.md §6.3  "표면 12 → 13"       (G3 항목)
   enode/README.md       "표면 13개를 먼저 고정했다"

셋 다 기록한다. 어느 것이 맞는지, 무엇을 고칠지는 이 문서가 판정하지 않는다.

없는 것

  • OpenAPI / Swagger 없음. 정본 문서가 "OpenAPI 로 굳히지 않는다" 를 명시했다
  • godoc 외의 생성 문서 없음
  • GET /v1/nodes 계열 없음 — §7 이 일부러 기각한 것
  • /retry · /queue 없음