enode — 컴포넌트 인벤토리
15개 패키지 전수. 각 항목의 책임 · 의존 · 주요 파일 · 계측을 적는다.
컴포넌트 이름은 Go 패키지 경로를 그대로 쓴다.
목록 요약
| 컴포넌트 | 종류 | 비테스트 파일 | LOC | 교차 커버리지 |
|---|---|---|---|---|
cmd/mediator |
실행파일 | 2 | 405 | 0.0% |
cmd/enode |
실행파일 | 3 | 269 | 0.0% |
cmd/enodectl |
실행파일 | 4 | 531 | 0.0% |
cmd/runctl |
실행파일 | 1 | 423 | 0.0% |
cmd/iapadapter |
실행파일 | 7 | 2,436 | 19.1% |
internal/api |
라이브러리 | 1 | 4,172 | 65.2% |
internal/store |
라이브러리 | 12 | 4,787 | 81.0% |
internal/contract |
라이브러리 | 5 | 3,565 | 88.5% |
internal/enode |
라이브러리 | 25 | 8,254 | 67.5% |
internal/match |
라이브러리 | 1 | 408 | 97.5% |
internal/record |
라이브러리 | 1 | 676 | 81.5% |
internal/schema |
라이브러리 | 1 | 339 | 75.7% |
internal/config |
라이브러리 | 2 | 481 | 55.9% |
internal/runctl |
라이브러리 | 1 | 345 | 37.0% |
internal/build |
라이브러리 | 1 | 120 | 75.0% |
LOC 는 테스트 포함. 커버리지는 -coverpkg=./... 교차 측정치이고 근거는
code-quality-assessment.md 에 있다.
실행파일
cmd/mediator
책임 — 중앙 서버 프로세스. 설정 로드 → 토큰 부트스트랩 → store.Open →
Migrate → reaper 고루틴 기동 → http.Server 기동 → graceful shutdown.
의존 — internal/api · internal/store · internal/record ·
internal/config · internal/build
파일 — main.go(57 statements) · setup.go(154 statements, runSetup 185줄)
주목 — WriteTimeout 을 일부러 걸지 않는다(main.go:114-117). claim 롱폴이
최대 7,200초 대기하므로 쓰기 타임아웃이 있으면 롱폴이 잘린다. 저장소에서
http.Server 를 만드는 유일한 자리(main.go:112)다.
cmd/enode
책임 — 자원이 있는 기계에 상주하는 노드 프로세스. 신원 유도 → 로컬 잠금 →
두 고루틴(광고 루프 + claim 워커) 기동.
의존 — internal/enode · internal/build
파일 — main.go(80 statements, main 173줄) · hook.go(22 statements) ·
setup.go
주목
- 기동마다 새
instance를 만든다(main.go:120-128). 이것이ADR-029/ADR-030
의 "claim 응답 유실 → 영구CLAIMED" 위험을 닫는 절반이다 - 롱폴 클라이언트는 타임아웃이 없는
&http.Client{}다(main.go:132-136) - 하위명령
hook stop이 곧R6의 종료 훅이다. 어떤 실패에도 종료코드 0
(hook.go:53) --once·--ready-file이 탄력 노드(단발 기동)용
cmd/enodectl
책임 — 한 기계의 enode 인스턴스 관리. setup · list · id · start ·
stop · logs · status · --version. HTTP 를 부르지 않는다.
의존 — internal/enode · internal/build
파일 — main.go(194 statements) · setup.go · proc_unix.go ·
proc_windows.go
주목 — 셸 스크립트판이 node_id 를 다시 계산하던 것을 없애려고 Go 로
옮겼다. 신원 유도 로직이 한 곳(internal/enode)에만 있게 된다.
cmd/runctl
책임 — 사용자 CLI. 무상태. 8개 하위명령과 4단 종료코드(0~3).
의존 — internal/runctl · internal/build
파일 — main.go(224 statements, run 250줄, usage :402)
주목 — permute() 가 플래그를 앞으로 모아 runctl submit x.json --wait
순서를 받는다. 종료코드가 "Run 실패(1)" 와 "요청 거절(2)" 와 "도달 불가(3)" 를
가르므로 스크립트가 세 상황을 구분할 수 있다.
cmd/iapadapter
책임 — It's a Plan 이슈 트래커 어댑터(ADR-040). 이슈를 읽어 Run 계약을
만들고, 진행과 결과를 이슈 코멘트로 되돌려 놓는다. 바깥을 아는 유일한 부품.
의존 — 내부 패키지 의존 0. 자기 안에서 HTTP 로 Mediator 와 말한다.
파일 — main.go · comment.go · config.go · contract.go ·
itsaplan.go · mediator.go · orchestrator.go
주목
- 어댑터를 갈아 끼워도 나머지가 안 흔들리도록 의존을 끊어 두었다
- Mediator 라우트 7개를 부른다.
GET /v1/runs/{id}/ledger를 부르는 유일한
클라이언트다(mediator.go:271) - 이 부품이
CLAUDE.md§2.3 의 「밖으로 나가느냐」 기준의 경계선에 서 있다 —
읽는 쪽이 하네스가 아니라 사람이다. 관측된 규약 틈은
code-quality-assessment.md참조
라이브러리 — 중앙
internal/api
책임 — Mediator 의 HTTP 표면. 15개 라우트, 인증, 오류 코드 체계, 롱폴,
blob 스트리밍.
의존 — internal/config · internal/contract · internal/match ·
internal/record · internal/schema · internal/store
파일 — api.go 단일 파일 830줄. 테스트는 api_test.go 3,342줄에 78개.
주목 — 라우터 라이브러리 의존 0. 라우트 등록이 Handler()(:53) 한 함수에
전부 있다. 15개 전부 s.auth(...) 로 감싸이고 예외 경로가 없다.
internal/store
책임 — PostgreSQL 상태 저장소. Run · Step · Node · Lease · Ask 의 생명주기
전부. 매칭 로직은 여기 없다.
의존 — internal/contract · internal/match · internal/record
파일 — 동사별로 12개로 갈려 있다.
| 파일 | 맡는 것 | 파일별 커버리지 |
|---|---|---|
store.go |
Open · Migrate · CreateRun · Capabilities |
80.5% |
claim.go (938줄) |
RenewLeases · ClaimStep · ReportStep · redeliver · FailRestarted |
84.5% |
verdict.go |
상태 상수 · Check · Verdict · Verify |
97.5% |
reap.go |
Reap · sealExpired · RunReaper · Cancel · SettleIfDone |
66.7% |
expand.go |
applyExpands(273줄) · maxContractVersions |
80.0% |
dispatch.go |
applyDispatch · propagateSkips · readDispatchValue |
77.0% |
acquire.go |
runAcquires · doAcquire · tryGrab |
76.0% |
release.go |
applyRelease |
86.2% |
rollback.go |
rollBack · loopBack · validateBack · untilMet |
82.0% |
seal.go |
sealRecord · ContractVersion · Manifest |
84.7% |
ask.go |
raiseAsks · PushAsks · AnswerStep · ExpireAsks |
79.6% |
observe.go |
Steps · Ledger · priorRuns · entriesOf |
78.9% |
schema.sql(144줄)이 //go:embed 로 이 패키지에 들어간다.
주목 — 90개 함수 전부가 테스트에 한 번 이상 들어간다. 자기 패키지 기준
커버리지(5.5%)와 교차 커버리지(81.0%)의 차이가 큰 이유는
code-quality-assessment.md 첫 절에 있다.
internal/contract
책임 — 계약 · 광고 타입과 그 검증. 넷의 공통 어휘. Mediator 와 노드가
공유하는 유일한 타입 정의다.
의존 — 없음(다른 내부 패키지를 부르지 않는다)
파일 — contract.go(1,724줄) · grammar.go · checkplan.go ·
planshape.go · advert.go
주목 — Contract.Validate()(contract.go:954)가 612줄이다. 계약 문법
전체의 단일 검증 지점이며, 역할 · 간선 · DAG · 스키마 경계 · 단계 종류 · 예약
이름 · dispatch 목적지 · ask 형태를 전부 여기서 본다. expands 로 자란 계약의
재검증 지점이기도 하다(expand.go:212 · :224).
internal/match
책임 — 요구 → 노드. 순수 함수. 입력 셋 · 출력 둘 · I/O 0.
의존 — internal/contract
파일 — match.go 단일
주목 — POST /v1/runs 와 POST /v1/runs/dry-run 이 같은 함수를 부른다.
거절 코드 둘(CodeNoCandidate = 422 영구 · CodeAllBusy = 409 일시)이 여기서
난다. attrCount(:34)가 속성이 적은 노드를 먼저 주는 정렬을 만든다
(ADR-027). 이 저장소에서 가장 잘 덮인 컴포넌트(97.5%)다.
internal/record
책임 — Run Record. DB 가 아니라 파일시스템. 봉인 · blob · tar.
의존 — 없음
파일 — record.go 단일
주목 — 봉인은 파일 0444 · 디렉터리 0555 로 건다(:156 seal, 순서 이유가
주석에 있다). blob 의 「최신」은 (회차, 순번)이 가장 큰 것이고 회차가 파일
이름에 들어간다(01.1-name 꼴, :260 blobPath · :294 parseBlobName).
:193 unseal 의 주석이 운영 함의를 스스로 적는다 — "봉인은 삭제까지 막는다.
… 디스크가 차면 사람이 지우지도 못한다."
internal/schema
책임 — 형식 전용 JSON Schema 검증기.
의존 — 없음
파일 — schema.go 단일
주목 — CheckBoundary(:46)가 minimum · minLength 같은 품질 판정
키워드를 계약 단계에서 거절한다. 스키마가 제약할 수 있는 것은 형식뿐이고
판정은 success_when 이 한다는 경계를 코드로 세운 자리다.
internal/config
책임 — Mediator 설정(YAML) 로드와 기본값.
의존 — 없음
파일 — config.go 외 1
주목 — 사용자 경로가 /etc 를 이긴다(ADR-015 §4 의 우선순위).
:57 이 Lease 값 셋(ttl_seconds 3600 · renew_seconds 60 ·
not_after_factor 3)에 대해 "지금은 임시치이며 S4 에서 실측으로 고친다" 고
적는다. :29 가 Artifacts.Root 와 MaxBlobBytes(기본 10 MiB)를 든다.
라이브러리 — 노드
internal/enode
책임 — 노드 런타임 전부. 신원 · 잠금 · 탐지 · 광고 · claim 워커 · 하네스
어댑터 · 환경 화이트리스트 · 워크스페이스 · 산출물 수거 · 종료 훅.
의존 — internal/contract 하나뿐. internal/store 도 internal/api 도
import 하지 않는다 — 노드가 Mediator 내부를 못 보는 것이 컴파일러가 지키는
사실이다.
파일 — 25개 비테스트 · 20개 테스트(테스트 함수 137개). 이 저장소에서 파일이
가장 많은 패키지.
| 묶음 | 파일 |
|---|---|
| 신원 · 잠금 | identity.go repoid.go lock.go lock_unix.go lock_windows.go |
| 탐지 · 광고 | detect.go advertise.go leases.go |
| 실행 | claim.go(873줄) runner.go argv.go agent.go |
| 하네스 | harness.go claude.go |
| 환경 | env.go config.go paths.go |
| 워크스페이스 | workspace.go setup.go diff.go changed.go |
| 수거 | collect.go hook.go |
| 디스크 | disk_unix.go disk_windows.go |
주목
runner.go:54runHarness가 저장소의 유일한 exec 지점이다(R3)env.go:97harnessEnv는 블랙리스트가 아니라 빈 것에서 쌓아 올리는
화이트리스트다.ENODE_TOKEN이 하네스로 넘어가지 않는다(R1)claim.go(873줄)가 이 패키지에서 압도적으로 가장 안 덮인 파일이다(24.8%).
같은 패키지 다른 파일은 대부분 75~96%.execute(222줄) ·runAgentStep(123줄)
·uploadProduced·report재시도 루프 · 임대 워치독 고루틴(:498-514)이
여기 있고, 이것이S4위험의 노드 쪽 절반이다detect.go:20Detect가 광고하는 속성은os·host_arch·harness·
ws·repo·arch·board·tag+ 사람이 붙인 라벨이다.
R7이 규정한sandbox키가 없다(관측;architecture.md관측 2)
internal/runctl
책임 — runctl 의 HTTP 클라이언트. 12개 메서드로 8개 라우트를 부른다.
의존 — 없음
파일 — client.go 단일
주목 — 12개 메서드 중 테스트가 들어가는 것이 4개(37.0%)다.
internal/build
책임 — <cmd> --version 한 줄. 링커가 박거나 Go VCS 스탬프에서 읽는다.
의존 — 없음
파일 — build.go 단일
주목 — 다섯 실행파일 전부가 이것 하나를 공유한다. 버전 문자열이 두 곳에
적히지 않는다.
컴포넌트 간 의존 요약
cmd/mediator → api, store, record, config, build
cmd/enode → enode, build
cmd/enodectl → enode, build
cmd/runctl → runctl, build
cmd/iapadapter → (내부 의존 없음)
internal/api → config, contract, match, record, schema, store
internal/store → contract, match, record
internal/enode → contract
internal/match → contract
internal/{contract, record, schema, config, build, runctl} → (없음)
순환 0건. 방향은 언제나 바깥(표면 · 실행파일)에서 안(어휘 · 상태)으로만 흐른다.
소유권 — 어느 컴포넌트가 무엇을 소유하는가
| 개념 | 소유 컴포넌트 | 근거 |
|---|---|---|
| 계약 · 광고 타입 정의 | internal/contract |
넷이 공유하는 유일한 타입 |
| 배정 결정 | internal/match |
순수 함수, store 도 api 도 결정하지 않는다 |
| Run · Step · Lease 상태 | internal/store |
상태 상수와 전이가 전부 여기 |
| 봉인된 기록 | internal/record |
파일 권한이 불변성의 기계 |
| HTTP 표면과 오류 코드 | internal/api |
단일 ServeMux |
| 노드 신원 | internal/enode |
hash(email · hostname · realpath(config))[:12] |
| 하네스 실행 | internal/enode/runner.go |
유일한 exec 지점 |
| 바깥 트래커 | cmd/iapadapter |
유일하게 바깥을 안다 |
애매한 소유권이 관측되지 않았다 — 각 개념이 정확히 한 컴포넌트에 산다.