update roadmap and storage migrations

This commit is contained in:
toki 2026-06-13 19:25:16 +09:00
parent c9952509e9
commit 2a7c3ae9bd
6 changed files with 78 additions and 20 deletions

View file

@ -11,7 +11,7 @@ NomadCode가 `develop` 같은 source-of-truth branch 변경을 즉시 감지할
## 상태
[진행중]
[완료]
## 승격 조건
@ -47,7 +47,7 @@ Forgejo push event를 Gito 내부 revision event 입력으로 변환한다.
- [x] [watch-bootstrap] `POST /api/listeners/branches` in-memory branch watch bootstrap을 추가한다. 검증: `services/core/internal/controlplane` 테스트가 `nomadcode/develop` watch 등록 후 Forgejo push matching을 확인한다.
- [x] [event-record] watched branch만 in-memory event record로 남기고 unwatched branch는 `matched=false`로 처리한다. 검증: `services/core/internal/controlplane` 테스트가 unwatched branch push를 무시하는지 확인한다.
- [ ] [watch-store] branch watch와 revision cursor를 Postgres store로 옮긴다. 검증: restart 이후에도 watch/cursor가 유지되고 duplicate push가 idempotent하게 처리된다.
- [x] [watch-store] branch watch와 revision cursor를 Postgres store로 옮긴다. 검증: restart 이후에도 watch/cursor가 유지되고 duplicate push가 idempotent하게 처리된다.
### Epic: [nomadcode-consume] NomadCode event consumption contract
@ -56,17 +56,21 @@ NomadCode가 Gito branch event를 내부 runtime event로 받을 수 있게 계
- [x] [protosocket-event] `/proto-socket` WebSocket upgrade와 `event.subscribe`, `event.list` MVP action을 추가한다. 검증: `services/core/internal/controlplane` registry test가 event channel action 상태를 확인한다.
- [x] [contract] `gito.forgejo-branch-events.v1` 제공 계약을 만든다. 검증: `agent-contract/index.md`가 계약 id와 원문 경로를 가리킨다.
- [x] [connection-filter] proto-socket subscription payload 기준으로 connection별 event filtering을 추가한다. 검증: subscription filter unit test와 `event.subscribe` handler test가 repo/branch/action matching을 확인한다.
- [ ] [consumer-smoke] `../nomadcode`의 proto-socket client가 `branch.updated`를 수신하는 smoke 또는 fixture를 추가한다. 검증: NomadCode client/core 테스트가 `branch.updated` envelope를 wakeup signal로 해석한다.
- [x] [consumer-smoke] `../nomadcode`의 proto-socket client가 `branch.updated`를 수신하는 smoke 또는 fixture를 추가한다. 검증: NomadCode client/core 테스트가 `branch.updated` envelope를 wakeup signal로 해석한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 없음
- 상태: 승인됨
- 요청일: 2026-06-13
- 완료 근거:
- `consumer-smoke`: `agent-task/archive/2026/06/m-forgejo-branch-event-mvp/01_consumer_smoke/complete.log`의 Roadmap Completion PASS.
- `watch-store`: `agent-task/archive/2026/06/m-forgejo-branch-event-mvp/02+01_watch_store/complete.log`의 Roadmap Completion PASS.
- 모든 기능 Task가 evidence와 함께 `[x]` 상태라 Milestone을 `[검토중]` 완료 후보로 전환했다.
- 종료 전 코드 레벨 검토에서 기존 dev/runtime DB에 남은 legacy `revision_cursors_repo_id_fkey` 흡수 이슈를 발견해 작은 migration 보정과 회귀 테스트를 추가했고, 전체 검증을 재통과했다.
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
- 리뷰 코멘트: 없음
- [x] 사용자가 완료 결과를 확인했다
- [x] archive 이동을 승인했다
- 리뷰 코멘트: 코드 레벨 종료 검토 완료. 큰 이슈 없음. 작은 legacy FK 호환 보정 반영 후 archive 승인됨.
## 범위 제외
@ -84,7 +88,7 @@ NomadCode가 Gito branch event를 내부 runtime event로 받을 수 있게 계
- 선행 작업: Runtime Scaffold and Contract Baseline
- 후속 작업: Operation Event Outbox, Provider Adapter Foundation
- 확인 필요: 없음
- 현재 작업현황(2026-06-13): Gito 쪽 1차 in-memory slice는 구현되어 있다. Forgejo push callback, signature 검증, push payload normalization, branch watch bootstrap, in-memory event record, proto-socket `event.subscribe`/`event.list`, connection별 subscription filtering, `gito.forgejo-branch-events.v1` 제공 계약까지 반영했다. active task는 `agent-task/m-forgejo-branch-event-mvp/01_consumer_smoke/`와 `agent-task/m-forgejo-branch-event-mvp/02+01_watch_store/`가 생성되어 있으며, 둘 다 `complete.log`가 없어 Milestone 기능 Task는 미완료 상태로 유지한다.
- 검증 현황(2026-06-13): `[connection-filter]`까지 반영한 뒤 `bin/test`, `bin/lint`, `bin/build`, `cd services/core && go test ./...`, `cd services/core && go vet ./...`가 PASS했다. `bin/test`에서 `apps/client`는 Flutter toolchain/test scaffold 없음으로 skip됐다. `consumer-smoke``watch-store` active review stub에는 아직 구현 검증 출력과 PASS 판정이 없다.
- 남은 MVP 항목(우선순위): 1) `[consumer-smoke]` 구현/검증/코드리뷰 PASS, 2) `[watch-store]` Postgres persistence와 duplicate push idempotency hardening. `watch-store` plan은 `consumer-smoke``complete.log`를 선행 조건으로 둔다.
- 현재 작업 포커스: `../nomadcode`가 직접 소비할 수 있는 1차 MVP를 먼저 닫기 위해 `agent-task/m-forgejo-branch-event-mvp/01_consumer_smoke/` 구현과 리뷰 완료를 다음 실행 1순위로 둔다. `agent-task/m-forgejo-branch-event-mvp/02+01_watch_store/`는 consumer wakeup fixture가 PASS된 뒤 진행한다.
- 현재 작업현황(2026-06-13): Gito 쪽 Forgejo push callback, signature 검증, push payload normalization, branch watch routing, proto-socket event subscribe/list/filtering, 제공 계약, NomadCode consumer smoke, Postgres watch/cursor/delivery idempotency 검증까지 모두 완료했다. active task는 없고 완료 산출물은 `agent-task/archive/2026/06/m-forgejo-branch-event-mvp/01_consumer_smoke/`와 `agent-task/archive/2026/06/m-forgejo-branch-event-mvp/02+01_watch_store/`에 있다.
- 검증 현황(2026-06-13): `consumer-smoke`는 NomadCode proto-socket branch event service focused/full integration tests와 analyze, diff check가 PASS했다. `watch-store`는 code-server 동반 Postgres로 `GITO_TEST_DATABASE_URL="$DATABASE_URL" go test -v -count=1 ./internal/storage`를 skip 없이 PASS했고, legacy revision cursor FK migration 회귀 테스트도 PASS했다. `services/core` focused/full tests, `go vet`, `./bin/test`, `./bin/lint`, `./bin/build`, `git diff --check`가 PASS했다. NomadCode `flutter analyze --no-fatal-infos`는 exit 0이며 기존 Mattermost `avoid_print` info 13건만 남았다.
- 남은 MVP 항목(우선순위): 없음.
- 현재 작업 포커스: 완료 및 archive 이동 완료 후 다음 Milestone `Repo Registry and Workspace Lease`로 전환한다.

View file

@ -14,15 +14,15 @@ Gito의 초기 control plane을 build 가능한 modular monolith로 구성한다
완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다.
스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다.
- [완료] Forgejo Branch Event MVP
- 경로: `agent-roadmap/archive/phase/control-plane-foundation/milestones/forgejo-branch-event-mvp.md`
- 요약: Forgejo push webhook을 watched branch `branch.updated` event로 정규화하고 NomadCode가 proto-socket으로 받을 수 있는 MVP slice를 만든다.
- [검토중] Runtime Scaffold and Contract Baseline
- 경로: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md`
- 요약: Go command, Flutter control surface placeholder, contracts, agent-ops, README, architecture 문서를 실행 가능한 기준선으로 정리한다.
- [진행중] Forgejo Branch Event MVP
- 경로: `agent-roadmap/phase/control-plane-foundation/milestones/forgejo-branch-event-mvp.md`
- 요약: Forgejo push webhook을 watched branch `branch.updated` event로 정규화하고 NomadCode가 proto-socket으로 받을 수 있는 MVP slice를 만든다.
- [계획] Repo Registry and Workspace Lease
- [진행중] Repo Registry and Workspace Lease
- 경로: `agent-roadmap/phase/control-plane-foundation/milestones/repo-registry-and-workspace-lease.md`
- 요약: 관리 대상 repo와 workspace slot/lease를 Postgres source of truth로 저장하고 안전하게 할당한다.

View file

@ -11,7 +11,7 @@
## 상태
[계획]
[진행중]
## 승격 조건
@ -72,4 +72,3 @@ Gito가 관리할 repository와 기본 branch/workspace root/credential ref를
- 선행 작업: Runtime Scaffold and Contract Baseline
- 후속 작업: Platformless Git Operation Engine
- 확인 필요: 없음

View file

@ -138,6 +138,58 @@ func TestPostgresBranchEventStorePersistsWatchCursorAndDedupesDelivery(t *testin
}
}
func TestPostgresMigrationDropsLegacyRevisionCursorRepoFK(t *testing.T) {
dsn := os.Getenv("GITO_TEST_DATABASE_URL")
if dsn == "" {
t.Skip("GITO_TEST_DATABASE_URL not set; skipping Postgres integration test")
}
ctx := context.Background()
dsn = isolatedPostgresDSN(t, ctx, dsn)
legacyPool, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("open legacy pool: %v", err)
}
defer legacyPool.Close()
_, err = legacyPool.Exec(ctx, `
CREATE TABLE repos (
id TEXT PRIMARY KEY
);
CREATE TABLE revision_cursors (
repo_id TEXT NOT NULL REFERENCES repos(id),
branch TEXT NOT NULL,
revision TEXT NOT NULL,
observed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (repo_id, branch)
);
`)
if err != nil {
t.Fatalf("create legacy schema: %v", err)
}
migrationData, err := os.ReadFile("../../migrations/00001_initial.sql")
if err != nil {
t.Fatalf("read migration: %v", err)
}
store, err := storage.NewPgStore(ctx, dsn, string(migrationData))
if err != nil {
t.Fatalf("open store: %v", err)
}
defer store.Close()
cursor := core.RevisionCursor{
RepoID: "provider-repo-without-repos-row",
Branch: "develop",
Revision: "abc123",
ObservedAt: time.Now().UTC(),
}
if err := store.RevisionCursors().UpsertRevisionCursor(ctx, cursor); err != nil {
t.Fatalf("upsert cursor after legacy FK migration: %v", err)
}
}
func isolatedPostgresDSN(t *testing.T, ctx context.Context, dsn string) string {
t.Helper()

View file

@ -172,6 +172,7 @@ func TestInitialMigrationSupportsStoreBoundary(t *testing.T) {
// required column/constraint snippets
columnSnippets := []string{
"operation_id",
"ALTER TABLE revision_cursors DROP CONSTRAINT IF EXISTS revision_cursors_repo_id_fkey",
"UNIQUE (provider, repo_id, branch)",
"UNIQUE (provider, dedupe_key)",
}

View file

@ -55,6 +55,8 @@ CREATE TABLE IF NOT EXISTS revision_cursors (
PRIMARY KEY (repo_id, branch)
);
ALTER TABLE revision_cursors DROP CONSTRAINT IF EXISTS revision_cursors_repo_id_fkey;
CREATE TABLE IF NOT EXISTS branch_watches (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,