diff --git a/README.md b/README.md index 51e173b..046669d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ Flutter client는 control surface 경계만 잡혀 있다. - Go `server`, `worker` command build - health/readiness REST placeholder +- proto-socket registry와 binary `event.subscribe` / `branch.updated` event path +- Forgejo push callback, watched branch listener, branch event smoke/debug surface +- PostgreSQL-backed repo, workspace lease, operation, event, branch watch, revision cursor, provider delivery baseline - platformless `gitengine` 최소 테스트 - core domain DTO 후보 - provider-neutral `ChangeRequest` adapter 경계 @@ -26,13 +29,12 @@ Flutter client는 control surface 경계만 잡혀 있다. 아직 구현 전인 범위: -- 실제 proto-socket channel/action -- 실제 PostgreSQL store wiring +- `event` 외 proto-socket channel/action의 실제 command behavior - `agent_shell: path: ../agent-shell` package를 쓰는 Flutter host integration - `../iop` backend/runtime 결과를 Gito operation/event 계약으로 수렴시키는 연결 -- GitHub/GitLab/Gitea provider adapter +- GitHub/GitLab/Gitea provider adapter와 Forgejo PR/MR adapter - Flutter 화면 구성과 operation event 구독 ## 빠른 시작 @@ -161,9 +163,10 @@ Provider Adapters | --- | --- | --- | | `APP_ENV` | runtime 환경 이름, 기본값 `local` | 아니오 | | `HTTP_ADDR` | server listen address, 기본값 `:8080` | 아니오 | -| `DATABASE_URL` | PostgreSQL connection string | 실제 storage wiring 이후 필요 | +| `DATABASE_URL` | PostgreSQL connection string, 없으면 in-memory runtime 사용 | 아니오 | | `REDIS_URL` | optional Redis endpoint | 아니오 | | `PROTO_SOCKET_PATH` | proto-socket endpoint path, 기본값 `/proto-socket` | 아니오 | +| `FORGEJO_WEBHOOK_SECRET` | Forgejo push callback HMAC-SHA256 서명 키, 없으면 검증 생략 | 아니오 | | `WORKER_ENABLED` | worker 실행 여부, 기본값 `true` | 아니오 | Credential 값은 tracked 파일에 쓰지 않는다. 필요한 경우 `credential_ref`나 ignored local secret file로 연결한다. diff --git a/agent-contract/provided/gito-forgejo-branch-events-v1.md b/agent-contract/provided/gito-forgejo-branch-events-v1.md index 859f619..9f3be76 100644 --- a/agent-contract/provided/gito-forgejo-branch-events-v1.md +++ b/agent-contract/provided/gito-forgejo-branch-events-v1.md @@ -342,7 +342,7 @@ listener bootstrap, `/proto-socket`, `/api/listeners/branches`, `/api/events`를 | 변수 | 필수 | 기본값 | 설명 | | --- | --- | --- | --- | | `APP_ENV` | 아니오 | `development` | 앱 환경 | -| `GITO_PROTO_SOCKET_PATH` | 아니오 | `/proto-socket` | proto-socket WebSocket 경로 | +| `PROTO_SOCKET_PATH` | 아니오 | `/proto-socket` | proto-socket WebSocket 경로 | | `FORGEJO_WEBHOOK_SECRET` | 아니오 | (없으면 서명 검증 생략) | Forgejo webhook 서명 키 | | `DATABASE_URL` | 아니오 | (없으면 in-memory) | PostgreSQL DSN | diff --git a/services/core/internal/controlplane/router.go b/services/core/internal/controlplane/router.go index 4115642..89bfd26 100644 --- a/services/core/internal/controlplane/router.go +++ b/services/core/internal/controlplane/router.go @@ -168,10 +168,6 @@ func handleForgejoPush(cfg config.Config, runtime *Runtime) http.HandlerFunc { writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"}) return } - if eventName := webhookEventName(r); eventName != "" && eventName != "push" { - writeJSON(w, http.StatusAccepted, map[string]any{"accepted": true, "matched": false, "reason": "unsupported event"}) - return - } body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "failed to read payload"}) @@ -182,6 +178,10 @@ func handleForgejoPush(cfg config.Config, runtime *Runtime) http.HandlerFunc { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid forgejo signature"}) return } + if eventName := webhookEventName(r); eventName != "" && eventName != "push" { + writeJSON(w, http.StatusAccepted, map[string]any{"accepted": true, "matched": false, "reason": "unsupported event"}) + return + } payload, err := forgejo.ParsePushPayload(body) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid forgejo push payload"}) diff --git a/services/core/internal/controlplane/router_test.go b/services/core/internal/controlplane/router_test.go index 2a3289e..767fb1b 100644 --- a/services/core/internal/controlplane/router_test.go +++ b/services/core/internal/controlplane/router_test.go @@ -180,6 +180,24 @@ func TestForgejoPushIgnoresUnwatchedBranch(t *testing.T) { } } +func TestForgejoPushRequiresSignatureBeforeIgnoringUnsupportedEvent(t *testing.T) { + router := NewRouter(config.Config{ + AppEnv: "test", + ProtoSocketPath: "/proto-socket", + ForgejoWebhookSecret: "forgejo-secret", + }, slog.Default()) + + req := httptest.NewRequest(http.MethodPost, "/callbacks/forgejo/push", bytes.NewBufferString(`{"unsupported":true}`)) + req.Header.Set("X-Forgejo-Event", "repository") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Fatalf("status: got %d body=%s", rec.Code, rec.Body.String()) + } +} + func TestEventSubscribeRegistersConnectionFilter(t *testing.T) { dispatcher := protosocket.NewDispatcher() runtime := NewRuntime(nil)