refactor: move dev scripts to scripts/dev/ and update configurations

This commit is contained in:
toki 2026-05-28 19:42:54 +09:00
parent 5c3fef65e9
commit e02ae80ce6
15 changed files with 229 additions and 90 deletions

11
.gitignore vendored
View file

@ -5,19 +5,10 @@ agent-ops/rules/private/
# Local runtime/test artifacts
/iop.db
/*.log
/build/
/dist/
/.env
/temp_verif/
/bin/control-plane
/bin/edge
/bin/artifacts/
/bin/iop-control-plane
/bin/iop-edge
/bin/iop-node
/bin/iop-node-*
/bin/iop-worker
/bin/node
/bin/worker
# Flutter Portal artifacts
apps/portal/.dart_tool/

View file

@ -1,7 +1,14 @@
.PHONY: all build build-edge build-node build-node-target pack-node-target tidy test test-e2e test-openai-ollama proto proto-dart portal-test portal-build-web clean
.PHONY: all build build-edge build-node build-node-target pack-node-target pack-edge tidy test test-e2e test-openai-ollama proto proto-dart portal-test portal-build-web clean
GOFLAGS ?= -trimpath
BUILD_DIR ?= bin
BUILD_DIR ?= build
BUILD_BIN_DIR ?= $(BUILD_DIR)/bin
ARTIFACT_DIR ?= $(BUILD_DIR)/artifacts
PACK_DIR ?= $(BUILD_DIR)/packages
EDGE_TARGET ?= $(shell go env GOOS)-$(shell go env GOARCH)
EDGE_PACKAGE_NAME ?= iop-edge-$(EDGE_TARGET)
EDGE_PACKAGE ?= $(PACK_DIR)/$(EDGE_PACKAGE_NAME).tar.gz
EDGE_PACKAGE_TMP ?= $(BUILD_DIR)/.edge-package
NODE_TARGET ?= $(shell go env GOOS)-$(shell go env GOARCH)
NODE_TARGET_PARTS = $(subst -, ,$(NODE_TARGET))
NODE_GOOS = $(word 1,$(NODE_TARGET_PARTS))
@ -10,21 +17,35 @@ NODE_GOARCH = $(word 2,$(NODE_TARGET_PARTS))
all: build
build:
go build $(GOFLAGS) -o $(BUILD_DIR)/iop-edge ./apps/edge/cmd/edge
go build $(GOFLAGS) -o $(BUILD_DIR)/iop-node ./apps/node/cmd/node
mkdir -p $(BUILD_BIN_DIR)
go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-edge ./apps/edge/cmd/edge
go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node ./apps/node/cmd/node
build-edge:
go build $(GOFLAGS) -o $(BUILD_DIR)/iop-edge ./apps/edge/cmd/edge
mkdir -p $(BUILD_BIN_DIR)
go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-edge ./apps/edge/cmd/edge
build-node:
go build $(GOFLAGS) -o $(BUILD_DIR)/iop-node ./apps/node/cmd/node
mkdir -p $(BUILD_BIN_DIR)
go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node ./apps/node/cmd/node
build-node-target:
@test -n "$(NODE_GOOS)" && test -n "$(NODE_GOARCH)" || (echo "NODE_TARGET must be <goos>-<goarch>" >&2; exit 2)
GOOS=$(NODE_GOOS) GOARCH=$(NODE_GOARCH) go build $(GOFLAGS) -o $(BUILD_DIR)/iop-node-$(NODE_TARGET) ./apps/node/cmd/node
mkdir -p $(BUILD_BIN_DIR)
GOOS=$(NODE_GOOS) GOARCH=$(NODE_GOARCH) go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node-$(NODE_TARGET) ./apps/node/cmd/node
pack-node-target: build-edge build-node-target
cd $(BUILD_DIR) && ./iop-edge bootstrap pack --target $(NODE_TARGET) --node-binary ./iop-node-$(NODE_TARGET)
$(BUILD_BIN_DIR)/iop-edge bootstrap pack --target $(NODE_TARGET) --node-binary $(BUILD_BIN_DIR)/iop-node-$(NODE_TARGET) --output $(ARTIFACT_DIR)
pack-edge: build-edge
@test -d "$(ARTIFACT_DIR)/bootstrap" || (echo "missing node bootstrap artifacts; run make pack-node-target NODE_TARGET=<goos>-<goarch> first" >&2; exit 2)
rm -rf "$(EDGE_PACKAGE_TMP)"
mkdir -p "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/artifacts" "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/logs" "$(PACK_DIR)"
cp "$(BUILD_BIN_DIR)/iop-edge" "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/iop-edge"
cp -R "$(ARTIFACT_DIR)/." "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/artifacts/"
tar -C "$(EDGE_PACKAGE_TMP)" -czf "$(EDGE_PACKAGE)" "$(EDGE_PACKAGE_NAME)"
rm -rf "$(EDGE_PACKAGE_TMP)"
@echo "wrote $(EDGE_PACKAGE)"
tidy:
go mod tidy
@ -87,9 +108,6 @@ portal-build-web:
--dart-define=IOP_CONTROL_PLANE_WIRE_URL=ws://localhost:19080/portal
clean:
rm -f bin/iop-edge bin/iop-node bin/iop-control-plane bin/iop-worker
rm -f bin/iop-node-*
rm -f bin/node bin/edge bin/control-plane bin/worker
rm -rf bin/artifacts
rm -rf build
rm -rf dist
rm -f iop.db

View file

@ -13,8 +13,8 @@ last_rule_updated_at: 2026-05-28
## 포함 경로
- `Makefile` — 공식 test target, proto generation, 보조 smoke target을 선언하는 위치이다.
- `bin/edge.sh` — repo 내부 edge console/server 개발 진단 helper이다.
- `bin/node.sh` — repo 내부 node 연결 개발 진단 helper이다. field 사용자 기본 경로로 안내하지 않는다.
- `scripts/dev/edge.sh` — repo 내부 edge console/server 개발 진단 helper이다.
- `scripts/dev/node.sh` — repo 내부 node 연결 개발 진단 helper이다. field 사용자 기본 경로로 안내하지 않는다.
- `iop-edge bootstrap pack` — Node host bootstrap용 artifact 폴더를 준비하는 공식 command이다.
- `scripts/e2e-smoke.sh` — mock/real profile 기반 보조 edge-node smoke 검증이다.
- `scripts/e2e-openai-ollama.sh` — OpenAI-compatible Ollama 입력 표면 보조 smoke 검증이다.
@ -29,7 +29,7 @@ last_rule_updated_at: 2026-05-28
- 대상 패키지 테스트 — 변경한 패키지와 인접한 패키지의 빠른 회귀 검증이다.
- `go test ./...` — 저장소 전체 Go 테스트 회귀 검증이다.
- edge-node 개발 진단 흐름 검증 — `bin/edge.sh`와 `bin/node.sh`를 각각 실행하고, edge console에서 메시지 2회와 command 명령을 직접 보내 결과가 edge 화면에 도착하는지 확인하는 저수준 검증이다. field 사용자 기본 UX는 Edge가 제시한 bootstrap 명령이다.
- edge-node 개발 진단 흐름 검증 — `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 각각 실행하고, edge console에서 메시지 2회와 command 명령을 직접 보내 결과가 edge 화면에 도착하는지 확인하는 저수준 검증이다. field 사용자 기본 UX는 Edge가 제시한 bootstrap 명령이다.
- 보조 E2E smoke — 임시 설정과 mock adapter로 최소 생존을 빠르게 확인하는 보조 검증이다. 이 결과만으로 완료 처리하지 않는다.
- OpenAI-compatible Ollama smoke — `scripts/e2e-openai-ollama.sh`로 OpenAI HTTP 입력 표면이 edge service와 node adapter 경로로 수렴하는지 확인하는 보조 검증이다.
- full-cycle 실제 구동 — 비효율적이어도 관련 사용자 명령과 실행 cycle을 한 번씩 실제 entrypoint로 통과시키는 검증이다.
@ -39,9 +39,9 @@ last_rule_updated_at: 2026-05-28
## 유지할 패턴
- 테스트는 테스트 파일 변경 여부가 아니라 작업 영향 범위로 결정한다.
- 사용자 실행 파이프라인에 닿는 작업을 한 경우, 작업 완료 후 일반 Go 테스트와 변경 범위에 맞는 full-cycle 실제 구동을 검증한다. repo 내부 edge-node 진단에는 `bin/edge.sh` + `bin/node.sh`를 사용할 수 있지만, field 사용자 기본 UX는 bootstrap 명령으로 확인한다.
- 사용자 실행 파이프라인에는 `bin/**`, `apps/*/cmd/**`, `apps/*/internal/bootstrap/**`, edge-node transport/service/registry/input surface, adapter 실행/stream/cancel/status 경로, `configs/**`, `packages/config/**`, `packages/hostsetup/**`, 관련 protobuf 계약 변경이 포함된다.
- edge-node 개발 진단 흐름 검증은 `bin/edge.sh`와 `bin/node.sh`를 별도 프로세스로 직접 실행하고, edge console prompt에 명령을 한 줄씩 입력한 뒤 기대 출력이 도착한 것을 확인하고 다음 입력으로 넘어간다.
- 사용자 실행 파이프라인에 닿는 작업을 한 경우, 작업 완료 후 일반 Go 테스트와 변경 범위에 맞는 full-cycle 실제 구동을 검증한다. repo 내부 edge-node 진단에는 `scripts/dev/edge.sh` + `scripts/dev/node.sh`를 사용할 수 있지만, field 사용자 기본 UX는 bootstrap 명령으로 확인한다.
- 사용자 실행 파이프라인에는 `scripts/dev/**`, `Makefile`, `apps/*/cmd/**`, `apps/*/internal/bootstrap/**`, edge-node transport/service/registry/input surface, adapter 실행/stream/cancel/status 경로, `configs/**`, `packages/config/**`, `packages/hostsetup/**`, 관련 protobuf 계약 변경이 포함된다.
- edge-node 개발 진단 흐름 검증은 `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 별도 프로세스로 직접 실행하고, edge console prompt에 명령을 한 줄씩 입력한 뒤 기대 출력이 도착한 것을 확인하고 다음 입력으로 넘어간다.
- 메시지 검증 기준은 edge console에서 같은 session으로 메시지 2회를 보내고, 각 요청마다 `[edge] sent`, `[node-*-event] start`, 비어 있지 않은 `[node-*-message]`, `[node-*-event] complete`가 edge 화면에 표시되는 것이다.
- 완료 이벤트만으로 정상 판정하지 않는다. node 로컬 출력에 생성된 `[node-message]` payload가 edge console의 `[node-*-message]` 출력에 모두 표시되어야 하며, complete event는 모든 message payload가 edge에 도착한 뒤의 마감 신호로 본다.
- command 검증 기준은 edge console에서 `/nodes`와 변경 범위에 닿는 command를 직접 입력하고, node에서 온 결과가 edge 화면에 `[node-*-<command>]` 또는 명확한 성공/unsupported/error 출력으로 표시되는 것이다. CLI 경로 변경 시 최소 `/capabilities`, `/transport`, `/sessions`, persistent profile이면 `/terminate-session`을 확인한다.
@ -53,7 +53,7 @@ last_rule_updated_at: 2026-05-28
- field Node bootstrap의 사용자 명령은 `curl -fsSL <완성된-bootstrap-url> | bash -s <token>` 형태를 기준으로 한다. 다른 bootstrap/enrollment 작업에서도 같은 수준의 단일 token UX를 우선 적용하고, 예외가 필요하면 사용자에게 먼저 확인한다.
- repo 내부 edge-node 진단의 상세 수행 절차와 기능별 체크리스트는 `agent-ops/skills/project/e2e-smoke/SKILL.md`를 따른다.
- `make test-e2e`, `scripts/e2e-smoke.sh`, `scripts/e2e-openai-ollama.sh`는 보조 smoke 명령이다. 실행할 수 있으면 보조 확인으로 기록하되, full-cycle 실제 구동이나 field bootstrap 검증을 대체하지 않는다.
- `iop-edge bootstrap pack` 또는 내장 artifact server 변경 시 최소 현재 host target build를 실행하고 artifact 폴더 위치, checksum 생성, node bootstrap script가 positional token UX를 유지하는지 확인한다.
- `iop-edge bootstrap pack`, `make pack-edge`, 내장 artifact server 변경 시 최소 현재 host target build를 실행하고 archive 압축 해제, artifact 폴더 위치, checksum 생성, node bootstrap script가 positional token UX를 유지하는지 확인한다.
- 풀테스트에서는 실제 외부 CLI profile 검증을 필수로 수행한다. 환경, 계정, provider, 원격 endpoint 문제로 호출할 수 없거나 실패한 profile은 누락하지 말고 profile별 실패 또는 blocker로 보고한다.
- 작업 최종 보고에는 실행한 테스트 명령, repo 내부 edge-node 진단 수행 여부, 보조 E2E smoke 수행 여부, full-cycle 실제 구동 수행 여부를 명시한다. 수행하지 못한 필수 검증은 이유와 남은 위험을 함께 적는다.

View file

@ -23,7 +23,10 @@
- `proto/iop/` — IOP 메시지 계약 원본이다.
- `proto/gen/iop/` — protobuf 생성물이다.
- `configs/` — 앱별 YAML 설정 예시이다.
- `bin/` — 개발 보조 shell entrypoint와 로컬 바이너리 산출 위치이다. Edge/Node 운영 UX와 Node bootstrap artifact 준비는 `iop-edge`/`iop-node` 바이너리 command 표면에 모은다.
- `scripts/dev/` — repo 내부 개발 진단 helper 위치이다. field 사용자 기본 경로로 안내하지 않는다.
- `build/bin/` — 로컬 바이너리 산출 위치이다.
- `build/artifacts/` — Node bootstrap artifact 산출 위치이다.
- `build/packages/` — Edge host에 전달할 압축 배포 archive 산출 위치이다. archive는 `iop-edge``artifacts/`를 포함하고, Edge host에서 config 확정 후 Node bootstrap을 제공하는 흐름을 기준으로 한다.
- `scripts/` — 보조 E2E smoke와 입력 표면 검증 스크립트이다.
- `Makefile` — 빌드와 테스트 진입점을 정의한다.
- `docs/` — 사람용 최신 가이드만 둔다. 환경별 값은 `agent-ops/rules/private/`에 두고, `rules.md`는 라우터로만 쓴다.
@ -46,7 +49,7 @@
- 새 node 어댑터는 `runtime.Adapter`를 구현하고 `apps/node/internal/bootstrap/module.go`에서 registry에 등록한다.
- 내부 실행 요청과 상태 저장에서는 `adapter`, `target`, `execution` 용어를 우선한다. `model`은 외부 API 호환이나 legacy placeholder일 때만 허용한다.
- Control Plane은 Node를 직접 연결/스케줄링하지 않고 Edge를 통해 시스템을 제어한다. Edge는 자신의 로컬 런타임 상태와 Node registry를 소유한다.
- 사용자 실행, 로컬/dev 배포, field 테스트, 임시 Control Plane 대체 흐름은 사용법을 `bin/`, `scripts/`, 별도 dev deploy 바이너리, 모델용 skill로 흩뜨리지 않고 `iop-edge``iop-node` command 표면에 모은다. helper script가 필요해도 공식 사용자 경로가 되면 안 된다.
- 사용자 실행, 로컬/dev 배포, field 테스트, 임시 Control Plane 대체 흐름은 사용법을 `scripts/dev/`, 보조 smoke script, 별도 dev deploy 바이너리, 모델용 skill로 흩뜨리지 않고 `iop-edge``iop-node` command 표면에 모은다. helper script가 필요해도 공식 사용자 경로가 되면 안 된다.
- Control Plane이 없는 테스트/개발 배포 단계에서는 `edge.yaml`을 테스트용 Control Plane source of truth로 본다. Node별 id/alias/token/adapter/runtime 설정은 Edge config의 `nodes[]`에서 관리한다.
- Node 사용자 UX는 bootstrap 명령 하나로 끝나야 한다. 사용자가 `node.yaml`을 만들거나 편집하거나 `iop-node serve --config ...`를 직접 실행하는 흐름을 기본 경로로 두지 않는다. bootstrap 내부에서 임시 상태나 설정 파일을 만들 수 있더라도 이는 구현 세부이며 사용자 가이드와 기본 운영 UX에 노출하지 않는다.
- 바이너리 배포 UX는 repo checkout 위치에 묶이지 않아야 한다. `iop-edge`/`iop-node`는 아무 작업 디렉터리에서 실행 가능해야 하며, 로컬/dev bundle에서는 Edge 설정을 바이너리와 같은 디렉터리의 `edge.yaml` 같은 구조화된 config에 모으는 방향을 우선한다. Node 쪽은 별도 사용자 config 파일이 아니라 Edge가 제공하는 bootstrap으로 연결한다.
@ -75,7 +78,7 @@
| `packages/**` | platform-common | `agent-ops/rules/project/domain/platform-common/rules.md` |
| `proto/**` | platform-common | `agent-ops/rules/project/domain/platform-common/rules.md` |
| `configs/**` | platform-common | `agent-ops/rules/project/domain/platform-common/rules.md` |
| `bin/**` | testing | `agent-ops/rules/project/domain/testing/rules.md` |
| `scripts/dev/**` | testing | `agent-ops/rules/project/domain/testing/rules.md` |
| `scripts/e2e-*.sh` | testing | `agent-ops/rules/project/domain/testing/rules.md` |
| `Makefile` | testing | `agent-ops/rules/project/domain/testing/rules.md` |
@ -85,7 +88,7 @@
## 스킬 라우팅
- 사용자 실행 파이프라인 검증, repo 내부 edge-node 진단, 메시지 2회 왕복, edge command 응답, 보조 E2E smoke, full-cycle 실제 구동, `bin/edge.sh`/`bin/node.sh` 진단 테스트: `agent-ops/skills/project/e2e-smoke/SKILL.md`
- 사용자 실행 파이프라인 검증, repo 내부 edge-node 진단, 메시지 2회 왕복, edge command 응답, 보조 E2E smoke, full-cycle 실제 구동, `scripts/dev/edge.sh`/`scripts/dev/node.sh` 진단 테스트: `agent-ops/skills/project/e2e-smoke/SKILL.md`
- field 테스트 포트, artifact/bootstrap HTTP, 외부 테스트 환경: private rule 라우팅을 따른다.
- bootstrap/install UX, Agent Bootstrap, OTO 등록, Control Plane enrollment: `testing` domain rule과 private rule 라우팅을 따른다.
- 반복 작업이 확인되면 `agent-ops/skills/project/<skill-name>/SKILL.md`를 생성하고 이 표에 등록한다.

View file

@ -1,19 +1,19 @@
---
name: e2e-smoke
version: 1.1.0
description: bin/edge.sh와 bin/node.sh 기반 repo 내부 edge-node 진단, 보조 E2E smoke, 실제 외부 CLI 통합 검증 절차
description: scripts/dev/edge.sh와 scripts/dev/node.sh 기반 repo 내부 edge-node 진단, 보조 E2E smoke, 실제 외부 CLI 통합 검증 절차
---
# e2e-smoke
## 목적
repo 내부 edge-node 진단이 필요할 때 `bin/edge.sh`와 `bin/node.sh`를 각각 실행하고, edge console에서 메시지 2회와 command 명령을 직접 보내 결과가 edge 화면에 표시되는지 검증한다. field 사용자 기본 UX는 Edge가 제시한 bootstrap 명령이며, `bin/*` helper를 공식 사용자 경로로 안내하지 않는다. `make test-e2e``scripts/e2e-smoke.sh`는 최소 생존을 빠르게 확인하는 보조 smoke일 뿐이며, 사용자 실행 파이프라인 변경의 완료 기준이 아니다.
repo 내부 edge-node 진단이 필요할 때 `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 각각 실행하고, edge console에서 메시지 2회와 command 명령을 직접 보내 결과가 edge 화면에 표시되는지 검증한다. field 사용자 기본 UX는 Edge가 제시한 bootstrap 명령이며, `scripts/dev/*` helper를 공식 사용자 경로로 안내하지 않는다. `make test-e2e``scripts/e2e-smoke.sh`는 최소 생존을 빠르게 확인하는 보조 smoke일 뿐이며, 사용자 실행 파이프라인 변경의 완료 기준이 아니다.
## 언제 호출할지
- 사용자 실행 파이프라인에 닿는 작업을 완료한 뒤 검증 단계에 들어갈 때
- `bin/**`, `apps/*/cmd/**`, `apps/*/internal/bootstrap/**`, edge-node transport/service/registry, adapter 실행/stream/cancel/status 경로를 변경했을 때
- `scripts/dev/**`, `Makefile`, `apps/*/cmd/**`, `apps/*/internal/bootstrap/**`, edge-node transport/service/registry, adapter 실행/stream/cancel/status 경로를 변경했을 때
- `configs/**`, `packages/config/**`, 관련 protobuf 계약 변경이 edge-node 실행 방식에 영향을 줄 때
- `make test-e2e` 또는 보조 E2E smoke 스크립트를 만들거나 갱신할 때
@ -27,7 +27,7 @@ repo 내부 edge-node 진단이 필요할 때 `bin/edge.sh`와 `bin/node.sh`를
## 먼저 확인할 것
- [ ] `agent-ops/rules/project/domain/testing/rules.md`를 읽고 이번 작업에 필요한 repo 내부 edge-node 진단 또는 field bootstrap 검증 범위를 확인한다.
- [ ] `bin/edge.sh`와 `bin/node.sh`는 repo 내부 진단 helper이며 field 사용자 기본 entrypoint가 아님을 확인한다.
- [ ] `scripts/dev/edge.sh`와 `scripts/dev/node.sh`는 repo 내부 진단 helper이며 field 사용자 기본 entrypoint가 아님을 확인한다.
- [ ] `make test-e2e` 같은 고정 smoke 명령은 보조 확인으로만 취급한다.
- [ ] 기본 `configs/*.yaml`을 오염시키지 않도록 임시 config 또는 환경 변수 override 전략을 정한다.
- [ ] 실제 외부 CLI 검증이 필요한 경우 command 경로, 로그인/계정 상태, provider 설정, workspace 권한이 준비되어 있는지 확인한다.
@ -36,18 +36,18 @@ repo 내부 edge-node 진단이 필요할 때 `bin/edge.sh`와 `bin/node.sh`를
1. **검증 범위 결정**
- 변경 파일이 사용자 실행 파이프라인에 닿는지 확인한다.
- 필수 검증은 `go test ./...` 또는 대상 패키지 테스트와 변경 범위에 맞는 full-cycle 실제 구동이다. repo 내부 edge-node 진단에는 `bin/edge.sh`와 `bin/node.sh`를 사용할 수 있다.
- 필수 검증은 `go test ./...` 또는 대상 패키지 테스트와 변경 범위에 맞는 full-cycle 실제 구동이다. repo 내부 edge-node 진단에는 `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 사용할 수 있다.
- 보조 smoke는 실행할 수 있으면 기록하되, 통과만으로 완료 처리하지 않는다.
- 풀테스트에서는 실제 외부 CLI profile 검증을 full-cycle에 포함한다.
2. **repo 내부 edge-node 진단 준비**
- edge와 node는 `bin/edge.sh`, `bin/node.sh`를 각각 별도 프로세스로 실행한다. 이 흐름은 내부 진단용이며 사용자-facing field bootstrap 안내가 아니다.
- edge와 node는 `scripts/dev/edge.sh`, `scripts/dev/node.sh`를 각각 별도 프로세스로 실행한다. 이 흐름은 내부 진단용이며 사용자-facing field bootstrap 안내가 아니다.
- 임시 edge/node config 또는 환경 변수 override를 사용하고, edge listen 주소와 node edge 주소는 충돌을 피하기 위해 임시 포트를 사용한다.
- 검증용 target은 변경 범위에 맞춘다. 외부 CLI 자체가 검증 대상이 아니면 deterministic CLI profile을 사용해 메시지 출력 내용을 분명하게 만든다.
3. **startup/register cycle**
- 먼저 `bin/edge.sh`를 실행해 edge console prompt가 뜨는지 확인한다.
- 다른 프로세스에서 `bin/node.sh`를 실행해 node가 edge에 연결되는지 확인한다.
- 먼저 `scripts/dev/edge.sh`를 실행해 edge console prompt가 뜨는지 확인한다.
- 다른 프로세스에서 `scripts/dev/node.sh`를 실행해 node가 edge에 연결되는지 확인한다.
- edge 화면에 `[node-*-event] connected reason="registered"`가 표시되어야 한다.
- edge console에서 `/nodes`를 입력하고 node ID와 alias가 표시되어야 한다.
@ -132,7 +132,7 @@ terminated session default node=test-node
## 실행 결과 검증
- [ ] 일반 Go 테스트 또는 대상 패키지 테스트 결과가 보고되었는가
- [ ] `bin/edge.sh`와 `bin/node.sh`를 각각 실행한 repo 내부 edge-node 진단 결과가 보고되었는가
- [ ] `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 각각 실행한 repo 내부 edge-node 진단 결과가 보고되었는가
- [ ] node register와 `/nodes` 결과가 edge 화면에 표시되었는가
- [ ] 같은 session에서 메시지 2회가 각각 sent/start/message/complete로 표시되었는가
- [ ] node 로컬 `[node-message]` payload가 edge `[node-*-message]` 출력에 모두 표시되었는가

View file

@ -28,7 +28,7 @@ ops console은 edge-local diagnostic surface이고, HTTP/API는 central/remote m
## 원격 CLI adapter 개발 진단 흐름
field 사용자 기본 경로는 `iop-edge`가 제시한 bootstrap 명령으로 Node를 연결하는 흐름이다. `bin/edge.sh`와 `bin/node.sh`는 repo 내부 개발 진단 helper이며 공식 사용자 UX로 안내하지 않는다.
field 사용자 기본 경로는 `iop-edge`가 제시한 bootstrap 명령으로 Node를 연결하는 흐름이다. `scripts/dev/edge.sh`와 `scripts/dev/node.sh`는 repo 내부 개발 진단 helper이며 공식 사용자 UX로 안내하지 않는다.
개발 진단의 기준은 `make test-e2e`나 smoke 통과만이 아니라, edge console에서 메시지와 command를 직접 보내 결과가 edge 화면에 표시되는지 확인하는 것이다.
구체적인 내부 진단 절차는 `agent-ops/skills/project/e2e-smoke/SKILL.md`로 모은다.
`make test-e2e` 명령은 `scripts/e2e-smoke.sh`를 실행하는 보조 smoke이며, 최소 생존 확인에는 쓸 수 있지만 완료 기준을 대체하지 않는다.
@ -104,6 +104,9 @@ sudo iop-edge setup --dry-run --binary /usr/local/bin/iop-edge
sudo iop-edge setup --enable --start --binary /usr/local/bin/iop-edge
```
Edge 배포 archive는 repo root에서 `make pack-node-target NODE_TARGET=<goos>-<goarch>``make pack-edge`로 만든다.
archive에는 `iop-edge`와 Node bootstrap용 `artifacts/`가 들어가며, Edge host에서 `config init``node register`를 수행해 현재 host 기준 bootstrap URL을 확정한다.
`setup``--config` 기본값은 `/etc/iop/edge.yaml`이다. dev 단계 명령(`serve`, `console`, `config init/print/check`, `env`, `node register`)의 root persistent `--config` 기본값은 bundle-local `edge.yaml` (없을 시 executable-adjacent `edge.yaml`, 그 다음 repo dev fallback `configs/edge.yaml`)로 확인된다. 기존 설정 파일은 그대로 두며 `--overwrite-config`를 지정해야 덮어쓴다.
## Console 명령

View file

@ -609,8 +609,18 @@ func packNodeBootstrap(opts nodeBootstrapPackOptions) error {
return fmt.Errorf("mkdir target dir: %w", err)
}
dstNode := filepath.Join(targetDir, "iop-node")
if err := copyFile(opts.NodeBinary, dstNode, 0o755); err != nil {
return fmt.Errorf("copy iop-node: %w", err)
same, err := sameFile(opts.NodeBinary, dstNode)
if err != nil {
return fmt.Errorf("stat iop-node: %w", err)
}
if same {
if err := os.Chmod(dstNode, 0o755); err != nil {
return fmt.Errorf("chmod iop-node: %w", err)
}
} else {
if err := copyFile(opts.NodeBinary, dstNode, 0o755); err != nil {
return fmt.Errorf("copy iop-node: %w", err)
}
}
sum, err := fileSHA256(dstNode)
if err != nil {
@ -635,6 +645,21 @@ func packNodeBootstrap(opts nodeBootstrapPackOptions) error {
return nil
}
func sameFile(a, b string) (bool, error) {
ai, err := os.Stat(a)
if err != nil {
return false, err
}
bi, err := os.Stat(b)
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
if err != nil {
return false, err
}
return os.SameFile(ai, bi), nil
}
func copyFile(src, dst string, mode os.FileMode) error {
in, err := os.Open(src)
if err != nil {
@ -758,6 +783,26 @@ var tokenGenerator = func() string {
return uuid.NewString()
}
func refreshBootstrapForExistingArtifact(cfg *config.EdgeConfig, target, artifactURL string) error {
outputDir := resolveBootstrapArtifactDir(cfg.Bootstrap.ArtifactDir)
nodeBinary := filepath.Join(outputDir, target, "iop-node")
if _, err := os.Stat(nodeBinary); err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return fmt.Errorf("stat node artifact: %w", err)
}
adv := resolveAdvertiseHost(cfg.Server.AdvertiseHost)
return packNodeBootstrap(nodeBootstrapPackOptions{
NodeBinary: nodeBinary,
OutputDir: outputDir,
Target: target,
Version: version.Version,
ArtifactBaseURL: artifactURL,
EdgeAddr: addressFor(adv.Host, cfg.Server.Listen),
})
}
func validateEdgeConfig(cfg *config.EdgeConfig) error {
// 1. Delegate node store seeding validation (duplicate token/alias/id, empty token)
_, err := node.LoadFromConfig(cfg.Nodes)
@ -979,6 +1024,9 @@ directly in the default flow.`,
if err := validateEdgeConfig(&cfg); err != nil {
return fmt.Errorf("validate node register: %w", err)
}
if err := refreshBootstrapForExistingArtifact(&cfg, target, artifactURL); err != nil {
return fmt.Errorf("refresh bootstrap artifacts: %w", err)
}
// Patch raw YAML to preserve defaults and other sections
outData, err := patchYAML(data, &cfg)

View file

@ -454,6 +454,60 @@ bootstrap:
}
}
func TestNodeRegisterRefreshesExistingBootstrapArtifact(t *testing.T) {
dir := t.TempDir()
artifactDir := filepath.Join(dir, "artifacts")
target := "darwin-arm64"
targetDir := filepath.Join(artifactDir, target)
if err := os.MkdirAll(targetDir, 0o755); err != nil {
t.Fatalf("mkdir target dir: %v", err)
}
if err := os.WriteFile(filepath.Join(targetDir, "iop-node"), []byte("fake-node-binary"), 0o755); err != nil {
t.Fatalf("write node artifact: %v", err)
}
cfgPath := filepath.Join(dir, "edge.yaml")
yamlStr := `server:
listen: "127.0.0.1:39090"
advertise_host: "edge.deploy.test"
bootstrap:
listen: "127.0.0.1:38080"
artifact_dir: "` + artifactDir + `"
nodes: []
`
if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs([]string{
"--config", cfgPath,
"node", "register", "mac-node",
"--target", target,
"--token", "test-token",
})
if err := root.Execute(); err != nil {
t.Fatalf("execute register: %v\n%s", err, out.String())
}
script, err := os.ReadFile(filepath.Join(artifactDir, "bootstrap", "node-"+target+".sh"))
if err != nil {
t.Fatalf("read refreshed script: %v", err)
}
if !strings.Contains(string(script), `DEFAULT_ARTIFACT_BASE_URL="http://edge.deploy.test:38080"`) {
t.Fatalf("script missing refreshed artifact URL:\n%s", script)
}
if !strings.Contains(string(script), `DEFAULT_EDGE_ADDR="edge.deploy.test:39090"`) {
t.Fatalf("script missing refreshed edge addr:\n%s", script)
}
if !strings.Contains(out.String(), `curl -fsSL http://edge.deploy.test:38080/bootstrap/node-darwin-arm64.sh | bash -s test-token`) {
t.Fatalf("unexpected register output:\n%s", out.String())
}
}
func TestResolveAdvertiseHostPrefersExplicit(t *testing.T) {
got := resolveAdvertiseHost("explicit.example.test")
if got.Host != "explicit.example.test" || got.Source != "explicit" {
@ -1010,7 +1064,7 @@ func TestHelpDiscoversEdgeLocalDevFlow(t *testing.T) {
}
// 2. Assert absence of non-official/legacy paths (verify they aren't recommended)
for _, unwanted := range []string{"bin/edge.sh", "bin/node.sh", "dev-deploy"} {
for _, unwanted := range []string{"scripts/dev/edge.sh", "scripts/dev/node.sh", "dev-deploy"} {
if strings.Contains(got, unwanted) {
t.Errorf("root help contains legacy path reference: %q", unwanted)
}

View file

@ -33,7 +33,7 @@ Edge가 제시한 bootstrap 명령 한 줄을 실행하면 Node binary 다운로
make build-node
# 버전 확인
./bin/iop-node version
./build/bin/iop-node version
# 저수준 직접 실행은 내부 진단에서만 사용한다.
```
@ -50,7 +50,7 @@ iop-node serve
iop-node version
```
원격 edge에 붙는 사용자/field 테스트는 Edge가 제시한 bootstrap 명령을 실행한다. repo root의 `bin/node.sh`는 과거 개발 진단 helper로만 취급하고, 문서나 runbook의 공식 사용자 경로로 안내하지 않는다.
원격 edge에 붙는 사용자/field 테스트는 Edge가 제시한 bootstrap 명령을 실행한다. repo root의 `scripts/dev/node.sh`는 과거 개발 진단 helper로만 취급하고, 문서나 runbook의 공식 사용자 경로로 안내하지 않는다.
edge 예시 설정은 cli adapter의 `opencode` profile을 사용한다. `configs/edge.yaml``console.target``claude`, `claude-tui`, `antigravity`, `codex`, `opencode`, `cline-dgx` 같은 profile 이름으로 바꾸면 같은 실행 파이프라인에서 다른 CLI target을 검증할 수 있다.

View file

@ -8,12 +8,12 @@
- `node.yaml` 생성, 편집, 확인
- `edge.yaml``nodes[]` 수동 작성
- `iop-node serve --config ...` 직접 실행
- `bin/*.sh` helper나 별도 dev deploy 바이너리를 공식 경로로 사용
- `scripts/dev/*.sh` helper나 별도 dev deploy 바이너리를 공식 경로로 사용
## 1. 빌드
## 1. 빌드와 Edge 배포 패키지
공식 빌드 진입점은 `make build` 하나로 둔다.
이 명령은 Edge와 Node를 함께 빌드하고, 현재 host에서 바로 쓸 수 있는 바이너리만 `bin/`에 맞춰 둔다.
이 명령은 Edge와 Node를 함께 빌드하고, 현재 host에서 바로 쓸 수 있는 바이너리만 `build/bin/`에 맞춰 둔다.
```bash
make build
@ -29,32 +29,51 @@ make build-node
산출물:
```text
bin/iop-edge
bin/iop-node
build/bin/iop-edge
build/bin/iop-node
```
## 2. Bundle 디렉터리 준비
Edge host에 배포할 때는 Node target artifact를 먼저 준비한 뒤 Edge archive를 만든다.
하나의 디렉터리에 `iop-edge``iop-node` 바이너리가 함께 있다고 가정한다.
```bash
make pack-node-target NODE_TARGET=darwin-arm64
make pack-edge
```
Intel Mac Node면 `NODE_TARGET=darwin-amd64`를 사용한다.
Edge만 새로 빌드한 경우에는 기존 `build/artifacts/`를 유지한 상태에서 `make pack-edge`만 다시 실행한다.
Node binary를 새로 빌드한 경우에는 `make pack-node-target NODE_TARGET=<goos>-<goarch>``make pack-edge`를 다시 실행한다.
배포 산출물:
```text
/opt/iop-dev/
iop-edge
iop-node
build/packages/iop-edge-<edge-os>-<edge-arch>.tar.gz
```
repo-local 테스트는 `bin/`을 그대로 bundle 디렉터리로 사용할 수 있다.
## 2. Edge 패키지 압축 해제
Edge host에서는 archive 하나만 옮긴 뒤 압축을 푼다.
```bash
cd bin
tar -xzf build/packages/iop-edge-$(go env GOOS)-$(go env GOARCH).tar.gz -C /opt
cd /opt/iop-edge-$(go env GOOS)-$(go env GOARCH)
```
별도 디렉터리에서 테스트할 때는 repo root에서 빌드된 바이너리만 옮긴 뒤 그 디렉터리에서 실행한다.
압축 해제 후 기본 구조:
```text
iop-edge
artifacts/
logs/
```
repo-local에서 archive 없이 확인할 때는 새 bundle 디렉터리에 Edge binary와 artifacts를 함께 둔다.
```bash
mkdir -p /opt/iop-dev
cp bin/iop-edge bin/iop-node /opt/iop-dev/
cd /opt/iop-dev
mkdir -p /tmp/iop-edge-dev
cp build/bin/iop-edge /tmp/iop-edge-dev/
cp -R build/artifacts /tmp/iop-edge-dev/
cd /tmp/iop-edge-dev
```
## 3. Edge config 생성
@ -102,30 +121,21 @@ connected nodes
<bundle-dir>/logs/
```
## 5. Bootstrap artifact 준비
## 5. Bootstrap artifact 확인
Node host에서 bootstrap 명령까지 테스트하려면 `iop-edge serve`가 제공할 artifact 폴더를 준비한다.
```bash
./iop-edge bootstrap pack
```
기본 산출물:
Edge archive에는 `make pack-node-target`으로 준비한 Node artifact가 들어 있어야 한다.
```text
<bundle-dir>/artifacts/
artifacts/
bootstrap/node-<os>-<arch>.sh
<os>-<arch>/iop-node
<os>-<arch>/SHA256SUMS
```
Mac Node host용 artifact가 필요하면 repo root에서 대상 platform을 지정해 pack한다.
`node register`는 해당 target의 `artifacts/<target>/iop-node`가 있으면 현재 `edge.yaml` 기준으로 bootstrap script와 checksum을 다시 생성한다.
즉 build machine에서 만든 archive를 다른 Edge host에 풀어도, Edge host에서 config를 확정한 뒤 등록하면 Node가 받을 URL과 Edge 주소가 현재 host 기준으로 맞춰진다.
```bash
make pack-node-target NODE_TARGET=darwin-arm64
```
Intel Mac이면 `NODE_TARGET=darwin-amd64`를 사용한다.
새 Node binary를 Edge host에서 직접 교체한 경우에만 `./iop-edge bootstrap pack --target <goos>-<goarch> --node-binary <path>`로 다시 pack한다.
## 6. Node 등록과 bootstrap 출력
@ -147,6 +157,7 @@ Ollama Node로 등록할 때:
```
이 명령은 `edge.yaml``nodes[]`를 생성 또는 갱신하고, 마지막에 Node host에서 실행할 bootstrap 명령을 출력한다.
`--target` 값은 `artifacts/<target>/`에 들어 있는 Node artifact와 같아야 한다.
출력 예:

View file

@ -2,7 +2,7 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
CONFIG_FILE="${IOP_EDGE_CONFIG:-$REPO_ROOT/configs/edge.yaml}"
cd "$REPO_ROOT"

View file

@ -2,7 +2,7 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
CONFIG_FILE="${IOP_NODE_CONFIG:-$REPO_ROOT/configs/node.yaml}"
cd "$REPO_ROOT"
@ -26,7 +26,7 @@ echo "[node] waiting for edge at $EDGE_HOST:$EDGE_PORT timeout=${WAIT_TIMEOUT}s"
deadline=$((SECONDS + WAIT_TIMEOUT))
while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/"$1"/"$2"' _ "$EDGE_HOST" "$EDGE_PORT" 2>/dev/null; do
if (( SECONDS >= deadline )); then
echo "[node] edge is not reachable at $EDGE_HOST:$EDGE_PORT; start ./bin/edge.sh first or update configs/node.yaml transport.edge_addr" >&2
echo "[node] edge is not reachable at $EDGE_HOST:$EDGE_PORT; start ./scripts/dev/edge.sh first or update configs/node.yaml transport.edge_addr" >&2
exit 1
fi
sleep "$WAIT_INTERVAL"

View file

@ -2,7 +2,7 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PORTAL_DIR="$REPO_ROOT/apps/portal"
WEB_HOST="${IOP_WEB_HOST:-0.0.0.0}"

View file

@ -23,6 +23,7 @@ cleanup() {
trap cleanup EXIT
PORT=$((31000 + RANDOM % 5000))
BOOTSTRAP_PORT=$((21000 + RANDOM % 5000))
OPENAI_PORT=$((36000 + RANDOM % 5000))
OLLAMA_PORT=$((41000 + RANDOM % 5000))
EDGE_METRICS_PORT=$((46000 + RANDOM % 5000))
@ -116,6 +117,9 @@ server:
listen: "127.0.0.1:$PORT"
metrics:
port: $EDGE_METRICS_PORT
bootstrap:
listen: "127.0.0.1:$BOOTSTRAP_PORT"
artifact_dir: "$TMP_DIR/artifacts"
openai:
enabled: true
listen: "127.0.0.1:$OPENAI_PORT"
@ -157,7 +161,7 @@ EOF
EDGE_OUT="$TMP_DIR/edge.out"
NODE_OUT="$TMP_DIR/node.out"
mkfifo "$TMP_DIR/edge_fifo"
IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/bin/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 &
IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/scripts/dev/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 &
EDGE_PID=$!
exec 3> "$TMP_DIR/edge_fifo"
EDGE_FD_OPEN=1
@ -165,7 +169,7 @@ EDGE_FD_OPEN=1
wait_port 127.0.0.1 "$PORT" "edge node transport"
wait_port 127.0.0.1 "$OPENAI_PORT" "edge openai api"
IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/bin/node.sh" > "$NODE_OUT" 2>&1 &
IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 &
NODE_PID=$!
deadline=$((SECONDS + 30))

View file

@ -4,11 +4,11 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
echo "[e2e] NOTE: auxiliary smoke only; completion requires bin/edge.sh + bin/node.sh user-flow verification."
echo "[e2e] NOTE: auxiliary smoke only; completion requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification."
if command -v shellcheck >/dev/null 2>&1; then
echo "[e2e] running shellcheck..."
shellcheck "$0" "$REPO_ROOT/bin/edge.sh" "$REPO_ROOT/bin/node.sh"
shellcheck "$0" "$REPO_ROOT/scripts/dev/edge.sh" "$REPO_ROOT/scripts/dev/node.sh"
else
echo "[e2e] shellcheck not found, skipping"
fi
@ -20,6 +20,7 @@ EDGE_CONFIG="$TMP_DIR/edge.yaml"
NODE_CONFIG="$TMP_DIR/node.yaml"
PORT=$((30000 + RANDOM % 10000))
BOOTSTRAP_PORT=$((20000 + RANDOM % 10000))
EDGE_METRICS_PORT=$((40000 + RANDOM % 10000))
NODE_METRICS_PORT=$((50000 + RANDOM % 10000))
PROFILE="${IOP_E2E_PROFILE:-mock}"
@ -65,6 +66,9 @@ server:
listen: "127.0.0.1:$PORT"
metrics:
port: $EDGE_METRICS_PORT
bootstrap:
listen: "127.0.0.1:$BOOTSTRAP_PORT"
artifact_dir: "$TMP_DIR/artifacts"
nodes:
- id: test-node
alias: test-node
@ -127,6 +131,9 @@ server:
listen: "127.0.0.1:$PORT"
metrics:
port: $EDGE_METRICS_PORT
bootstrap:
listen: "127.0.0.1:$BOOTSTRAP_PORT"
artifact_dir: "$TMP_DIR/artifacts"
nodes:
- id: test-node
alias: test-node
@ -164,7 +171,7 @@ NODE_OUT="$TMP_DIR/node_out"
rm -f "$TMP_DIR/edge_fifo"
mkfifo "$TMP_DIR/edge_fifo"
IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/bin/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 &
IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/scripts/dev/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 &
EDGE_PID=$!
exec 3> "$TMP_DIR/edge_fifo"
@ -178,7 +185,7 @@ while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/'"$PORT" 2>/dev/
sleep 0.5
done
IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/bin/node.sh" > "$NODE_OUT" 2>&1 &
IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 &
NODE_PID=$!
LAST_CMD_START_LINE=1
@ -549,5 +556,5 @@ if [ $FAIL -eq 1 ]; then
fi
echo "[e2e] Auxiliary smoke test PASSED."
echo "[e2e] Completion still requires bin/edge.sh + bin/node.sh user-flow verification."
echo "[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification."
exit 0