enode — 비즈니스 개요
리버스 엔지니어링 산출물.
/home/sunny/enode-review/enode/를f46e187시점에
읽고 적었다. 설계 정본이 아니다 — 정본은
aidlc/spaces/default/knowledge/documents/enode-design/INVARIANTS.md이고,
이 문서는 「구현에 무엇이 있는가」만 기술한다.
이 저장소가 무엇인가
AI-Native SDLC 실행 모델의 구현이다. Go 모듈 github.com/taeels/enode,
실행파일 다섯(mediator · enode · runctl · enodectl · iapadapter)과
라이브러리 열(internal/*)로 이루어진 단일 저장소다.
세 축이 시스템을 이룬다.
| 축 | 실행파일 | 하는 일 |
|---|---|---|
| 접수 | runctl |
사람이 Run 계약을 제출하고 상태 · Record · 취소를 본다. 무상태 |
| 중앙 | mediator |
계약을 검증하고 노드에 배정하고 상태를 소유한다. 추론을 하지 않는다 |
| 실행 | enode |
자원이 있는 기계에 상주하며 Mediator 로 연결을 걸어 단계를 당겨 실행한다 |
여기에 한 기계의 enode 인스턴스를 관리하는 enodectl 과, 바깥 이슈 트래커를
아는 유일한 부품인 iapadapter(ADR-040)가 붙는다.
해결하는 문제 — 자원이 추론 쪽으로 오지 않는다
식별된 자원은 개발보드와 개발자가 쌓아온 컨텍스트다. 둘 다 복제되지
않는다. 보드는 물리적으로 하나뿐이고, 컨텍스트는 그 사람의 기계와 머릿속에만
있다.
이 제품이 없으면 자원이 추론이 있는 곳으로 와야 한다. 기존에는 MCP 가 그
역할을 했고 세 가지가 걸렸다.
관리 MCP 서버가 발산한다
설치 사용자마다 깔아야 한다
경직성 컨텍스트 입도(granularity)를 제어할 수 없다
enode 는 방향을 뒤집는다 — 추상화 · 자원 노출 · 임대. 추론이 자원 쪽으로
간다. 그 뒤집기가 코드에서 관측되는 자리는 다음 셋이다.
- 노드가 연결을 건다.
internal/enode/전체에net.Listen도http.Server
도 0건이다. 저장소 전체에서 서버를 여는 자리는cmd/mediator/main.go:112
하나뿐이다. 자원이 있는 기계는 포트를 열지 않는다 - 노드끼리 만나지 않는다.
internal/enode/claim.go의 모든 HTTP 호출이
c.Base(Mediator)로만 간다. 단계 사이의 산출물도 Mediator 를 경유한다
(PUT .../blob/NAME로 올리고GET /v1/runs/RUN/blob/NAME으로 내린다) - 자원 점유는 중앙이 소유한다.
internal/store/schema.sql의
leases (node_id text PRIMARY KEY)한 줄이 「노드 하나는 동시에 한 Run」을
애플리케이션 로직이 아니라 데이터베이스 기본키로 강제한다
가치의 고정점 — dhseo 시나리오
dhseo 가 퇴근했다. 재현 환경이 그 자리에만 있다. candidate patch 를 거기서
검증해야 한다.
enode 는 부재중인 기능 담당자의 대리인 노릇을 한다. 오케스트레이터가
graphics 파트에서 병렬 실험이 필요할 때 자원을 획득하고(acquire), 실험을
설계하고(계약의 expands 로 계획을 위임), 진행하고(claim 워커가 하네스를
띄운다), 보고까지(Run Record 봉인 + GET record) 한다.
이 시나리오가 가치 논의의 고정점이다. 완결성을 판단하는 기준은 이 시나리오가
끝까지 도는가이지 「돌아간다」가 아니다.
시나리오의 각 동작이 코드에서 사는 자리:
| 시나리오 동작 | 구현 위치 |
|---|---|
| 자원 획득 | internal/store/acquire.go runAcquires · doAcquire · tryGrab |
| 부재자 기계에 배정 | internal/match/match.go:66 Match — 순수 함수, I/O 0 |
| 실험 설계 위임 | internal/store/expand.go:52 applyExpands (계약이 실행 중에 자란다) |
| 실험 진행 | internal/enode/claim.go Worker.execute → internal/enode/runner.go:54 runHarness |
| 사람에게 되묻기 | internal/store/ask.go + GET /v1/asks · POST .../answer |
| 보고 | internal/record/record.go 봉인(0444/0555) + GET /v1/runs/{id}/record (tar) |
| 자원 반납 | internal/store/release.go · internal/store/reap.go SettleIfDone |
핵심 기능 — 코드에 있는 것
Run 계약(Run Contract) 이 이 제품의 중심 개념이다. 사람이 JSON 하나를
제출하면 Mediator 가 그것을 단계의 방향 그래프로 펴서 노드에 나눠 준다.
- 두 종류의 단계 —
agent(하네스에 프롬프트를 주고 산출물을 받는다)와
run(명령을 실행한다). 둘 다이거나 둘 다 아니면 400
(internal/contract/contract.go:796Kind()) - 순서는 목록이 아니라 간선 —
needs로 의존을 적고, 기본값은 직전 단계다.
게이트 술어가internal/store/claim.go:277-289에 있다 - 계약이 실행 중에 자란다 —
expands로 계획을 위임하면 새 단계가 붙는다.
계약은 하나가 아니라 열(version 열) 이고 Record 에 전부 남는다 - 분기와 건너뜀 전파 —
dispatch로 갈래를 고르면 안 고른 갈래가 SKIPPED 로
전파된다 (internal/store/dispatch.go:118propagateSkips) - 구간 반복과 되먹임 재시도 —
loop·validate_with
(internal/store/rollback.go) - 실행 중 자원 증감 —
acquire로 늘리고release로 조기 반납한다 - 되묻기 — 에이전트가 사람에게 물을 수 있다. 인박스(
GET /v1/asks)가 정본이고
웹훅 푸시는 보조다 (internal/store/ask.go:102PushAsks) - 성공 판정은 기계적 조건만 —
success_when을internal/store/verdict.go:98
Verify가 순수 함수로 판정한다. DB 도 시계도 안 본다. 코드가 종료코드를
판정에 쓰지 않는다 (internal/enode/claim.go:585-593) - 봉인된 Run Record — 끝난 Run 의 기록은 파일 0444 · 디렉터리 0555 로 봉인되고
그 뒤의 쓰기는 410 으로 거절된다
도메인 어휘
이 저장소를 읽으려면 다음 명사가 필요하다. 전부 internal/contract 가 소유하며
네 실행파일의 공통 어휘다.
| 용어 | 뜻 | 정의 위치 |
|---|---|---|
| Run | 계약 하나의 실행 인스턴스 | internal/store/store.go:145-152 (상태 상수) |
| Contract | 무엇을 어떤 순서로 어디서 할지 적은 JSON | internal/contract/contract.go:59 |
| Step | 계약의 한 단계. agent 또는 run |
internal/contract/contract.go:441 |
| Node | 자원을 광고하는 실행 기계 | internal/enode/identity.go (신원 유도) |
| Advert | 노드가 자기 속성을 알리는 것 | internal/enode/detect.go:20 Detect |
| Lease | 어떤 Run 이 어떤 노드를 점유했다는 장부 | internal/store/schema.sql leases |
| Claim | 노드가 실행할 단계를 하나 집어오는 것 | internal/store/claim.go:246 ClaimStep |
| Run Record | 그 Run 에서 일어난 일의 봉인된 파일시스템 기록 | internal/record/record.go |
| Ledger | 시야의 세 축(발견 · 주입 · 축소) 기록 | internal/store/observe.go:96 |
| Ask | 실행 중 사람에게 던지는 질문 | internal/store/ask.go |
| Harness | 실제 추론을 돌리는 바깥 프로그램(claude CLI) |
internal/enode/harness.go:217 |
완결성의 합격 기준 — 슬라이스 S1~S10
README.md:20-32 의 표가 이 저장소의 합격 기준이다. 「돌아간다」가 아니라
발표자료 §15 의 O1~O9 중 무엇이 켜지는가로 잰다. S1~S7 까지가 스켈레톤이다.
| 슬라이스 | 켜는 것 | |
|---|---|---|
S1 |
계약 타입 + 매처 (순수 함수) | I/O 0. 넷의 공통 어휘 |
S2 |
Mediator: DB 스키마 + POST /v1/runs |
I5 · I1(=O7) |
S3 |
enode: 신원 + 로컬 잠금 + 광고 루프 | O8 · O9 |
S4 |
두 연결 — 하트비트 + claim 롱폴 + 명령 실행 |
O6 · I2 |
S5 |
계약 조건 대조 — success_when |
O4 |
S6 |
Record 봉인 + GET record (tar) |
O1 · O2 · O3 · I4 |
S7 |
runctl — 제출 · 상태 · Record · 취소 |
O5 · O7 |
| — | 여기까지가 스켈레톤 | |
S8 |
blob 별 모양 + 스키마 검증 | I4 |
S9 |
agent 어댑터 + 되먹임 재시도 루프 | |
S10 |
워크스페이스 준비 · GET /v1/capabilities |
README.md:34 는 S4 가 최대 위험이라고 적는다 — ADR-016(하트비트가 임대를
나른다)은 설계만 있고 한 번도 안 돌았으며, 미정 값 셋(임대 기간 · 갱신 주기 ·
not_after_factor)이 전부 거기서 정해진다. 이 리뷰의 계측은 그 진술과 같은 곳을
가리킨다: 노드 쪽 임대 워치독이 사는 internal/enode/claim.go 가 이 저장소에서
가장 안 덮인 실행 경로다(24.8%). 자세한 것은 code-quality-assessment.md.
바깥과 닿는 유일한 자리
cmd/iapadapter 만이 저장소 바깥(It's a Plan 이슈 트래커)을 안다. 내부 패키지에
의존하지 않고 자기 안에서 HTTP 로 Mediator 와 말한다 — 어댑터를 갈아 끼워도
나머지가 안 흔들리게 하려는 배치다. 이슈를 읽어 Run 계약을 만들고, Run 의
진행과 결과를 이슈 코멘트로 되돌려 놓는다.
이 문서의 근거
- 제품 의도와 dhseo 시나리오:
aidlc/spaces/default/knowledge/aidlc-shared/product-intent.md - 슬라이스 표와 배치:
enode/README.md:20-56 - 정본 문서 6종:
aidlc/spaces/default/knowledge/documents/enode-design/ - 나머지 전부:
enode/의 Go 소스 직접 독해 (f46e187)