From ea97e736086f85b087801dc72d8aaaa8a045e175 Mon Sep 17 00:00:00 2001 From: toki Date: Fri, 3 Jul 2026 15:51:38 +0900 Subject: [PATCH] update: agent-test files - refine dev rules, live-cycle smoke tests, and plane-dev --- agent-test/dev/rules.md | 12 +- agent-test/live-cycle-smoke.md | 204 ++++++++++++++++++++++++++++++++- agent-test/plane-dev.md | 34 +++++- 3 files changed, 244 insertions(+), 6 deletions(-) diff --git a/agent-test/dev/rules.md b/agent-test/dev/rules.md index 781993e..23b087e 100644 --- a/agent-test/dev/rules.md +++ b/agent-test/dev/rules.md @@ -1,6 +1,6 @@ --- test_env: dev -last_rule_updated_at: 2026-07-01 +last_rule_updated_at: 2026-07-03 --- # dev 테스트 규칙 @@ -16,6 +16,7 @@ last_rule_updated_at: 2026-07-01 - raw token, password, webhook secret, provider credential, private key는 ignored local profile 또는 remote secret store에서만 주입한다. - 컨테이너 내부 listen 포트는 제품 기본값을 우선 유지하고, workspace 충돌 관리는 host publish/direct smoke 포트로 처리한다. - 테스트용 Docker subnet을 새로 만들 때는 `192.168.0.0/24` 대역을 사용하지 않는다. +- Plane-origin full-cycle 검증은 `agent-test/live-cycle-smoke.md`를 따른다. 수동 Gito redelivery는 진단/복구 근거일 뿐 자동 Forgejo -> Gito 경로 통과 근거로 기록하지 않는다. ## 기본 환경 @@ -49,8 +50,17 @@ dev NomadCode의 Gito webhook target은 `http://127.0.0.1:18011/api/integrations - `GITO_DEVELOP_REPO_PATH`는 Core container에서 접근 가능한 checkout path여야 한다. - 계약 원문은 `agent-contract/outer/gito-branch-webhook-consumer-v1.md`와 Gito 제공 계약 `../gito/agent-contract/provided/gito-forgejo-branch-events-v1.md`를 함께 따른다. +## Forgejo -> Gito Webhook 기준 + +- dev Forgejo repository hook이 host Gito dev port로 delivery할 때 target은 `http://host.docker.internal:18061/callbacks/forgejo/push?repo_id=nomadcode` 형태일 수 있다. +- 이 경우 Forgejo app config `[webhook] ALLOWED_HOST_LIST`에는 기존 허용 host와 함께 `host.docker.internal`이 포함되어야 한다. +- allowlist가 막히면 Forgejo `hook_task.response_content`에 `webhook can only call allowed HTTP servers`가 남고, Core task는 git push 후 `develop_match` 대기에서 멈출 수 있다. +- allowlist를 조정할 때는 config 백업을 먼저 만들고, raw secret이나 token 값은 조회/출력/기록하지 않는다. +- Forgejo 컨테이너에서 `curl http://host.docker.internal:18061/healthz`가 성공해야 provider callback 네트워크가 열린 것으로 본다. + ## 라우팅 +- core / full-cycle / Plane-origin milestone authoring, git develop push, Forgejo/Gito branch.updated, Plane Todo projection: `agent-test/live-cycle-smoke.md` - core / smoke / services/core backend baseline: `agent-test/local/core-smoke.md` - mobile / smoke / apps/client Flutter baseline: `agent-test/local/mobile-smoke.md` - mobile / smoke / Android SDK remote runner and debug APK validation: `agent-test/local/mobile-android-sdk-smoke.md` diff --git a/agent-test/live-cycle-smoke.md b/agent-test/live-cycle-smoke.md index 5d3b062..df504cd 100644 --- a/agent-test/live-cycle-smoke.md +++ b/agent-test/live-cycle-smoke.md @@ -1,25 +1,29 @@ --- test_env: local +test_env_scope: local_and_dev_profiles test_profile: live-cycle-smoke domain: core verification_type: smoke -last_rule_updated_at: 2026-06-21 +last_rule_updated_at: 2026-07-03 --- # live-cycle-smoke -전체 webhook → authoring → develop match → Plane projection → slot 회수 사이클을 검증한다. +전체 webhook -> authoring -> git develop push -> Forgejo/Gito branch event -> Plane projection -> slot 회수 사이클을 검증한다. 이 문서는 절차 기술과 runtime-equivalent 근거를 함께 기록한다. ## 읽기 조건 - `live-webhook-task`, `webhook-auto-enqueue`, `live-creation-projection` 증거가 필요한 경우. +- dev profile에서 Plane-origin milestone 생성이 git에 등록되고 다시 Plane `Todo`로 이동하는지 검증하는 경우. - `agent-task/m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke` 구현/리뷰 시. ## 환경 -- host: standard remote runner `toki@toki-labs.com`, checkout `$HOME/agent-work/nomadcode`. -- Core server: Docker Compose (`services/core/docker-compose.yml`), host port `18010->8080`. +- host: standard remote runner `toki@toki-labs.com`. +- local profile checkout: `$HOME/agent-work/nomadcode`, Core host port `18010->8080`, Gito source `18060`. +- dev profile checkout: `$HOME/agent-work/nomadcode-dev`, Core host port `18011->8080`, Gito source `18061`. +- Core server: Docker Compose (`services/core/docker-compose.yml`). - Plane dev: `https://plane.toki-labs.com`, workspace `general`, project `NOMAD`. - Secrets: `.env.plane.local` (ignored), SOPS `secrets/nomadcode.dev.sops.yaml` (webhook secret). - runner checkout: 검증용 disposable mirror다. dirty worktree, uncommitted 변경, 임시 파일을 이유로 중단하지 않고 현재 대화 workspace의 요청 범위 상태와 같게 reset/clean/reclone 또는 파일 동기화한 뒤 테스트한다. @@ -30,6 +34,32 @@ last_rule_updated_at: 2026-06-21 - `PLANE_WEBHOOK_SECRET`, `PLANE_TOKEN` 등 secret은 `.env.plane.local` 또는 SOPS에서 로드한다. - Plane dev 서버가 `https://plane.toki-labs.com`에서 정상 동작 중이어야 한다. - workspace slot 행이 DB에 `available` 상태로 최소 1개 이상 있어야 한다. +- dev profile에서는 `agent-test/dev/rules.md`를 먼저 읽고, Core `18011`, Gito `18061`, Forgejo provider hook 경로가 모두 열려 있어야 한다. +- Forgejo repository hook이 `host.docker.internal:18061`로 Gito에 전달하는 환경이면 Forgejo `[webhook] ALLOWED_HOST_LIST`가 `host.docker.internal`을 허용해야 한다. + +## dev profile 프리플라이트 + +아래 확인은 secret 값을 출력하지 않는다. 실패하면 Plane work item을 새로 만들기 전에 환경을 먼저 고친다. + +```sh +# 1. Core/Gito readiness +curl -fsS http://127.0.0.1:18011/readyz +curl -fsS http://127.0.0.1:18061/readyz + +# 2. Gito branch watch / subscription +curl -fsS http://127.0.0.1:18061/api/listeners/branches +curl -fsS http://127.0.0.1:18061/api/webhook-subscriptions +# Expected: repo_id=nomadcode, branch=develop, event=branch.updated. + +# 3. Forgejo container -> Gito host endpoint reachability +docker exec forgejo curl -fsS --max-time 5 http://host.docker.internal:18061/healthz + +# 4. Forgejo webhook allowlist +docker exec forgejo sh -c 'grep -n "^ALLOWED_HOST_LIST" /data/gitea/conf/app.ini' +# Expected when hook target uses host.docker.internal: value includes host.docker.internal. +``` + +Gito watch 또는 subscription이 없으면 Gito 제공 계약의 `/api/listeners/branches`와 `/api/webhook-subscriptions` bootstrap 절차를 따른다. raw secret 대신 `secret_ref` 또는 환경 참조만 사용한다. ## Fixture IDs (non-secret) @@ -87,6 +117,107 @@ for t in d.get('tasks', []): # 9. slot 재사용 확인: 두 번째 smoke 티켓 생성 → 같은 slot_id 또는 새 available slot에서 reservation ``` +## dev profile 자동 경로 체크리스트 + +dev profile에서는 아래 순서가 모두 이어져야 Plane-origin full-cycle 통과로 본다. + +1. Plane에서 새 `Backlog + AGENT assignee` work item을 만든다. + - 제목/본문에는 유일한 timestamp를 넣는다. + - 요청 범위는 roadmap milestone 문서 생성처럼 작은 산출물로 제한한다. + - 생성 직후 work item UUID만 기록하고 token, webhook secret, private key는 기록하지 않는다. +2. Core task 생성과 authoring 진입을 확인한다. + +```sh +CORE_URL="http://127.0.0.1:18011" +WORK_ITEM_UUID="" +curl -fsS "$CORE_URL/api/tasks" | python3 -c " +import json, sys +wid='$WORK_ITEM_UUID' +for t in json.load(sys.stdin).get('tasks', []): + if t.get('external_id') == wid: + m=t.get('metadata') or {} + print('task_id:', t.get('id')) + print('status:', t.get('status')) + print('authoring_run_state:', m.get('authoring_run_state')) + print('push_request_state:', m.get('push_request_state')) + print('wait_type:', m.get('wait_type')) +" +``` + +3. git remote `develop`에 새 revision이 생겼는지 확인한다. + +```sh +cd "$HOME/agent-work/nomadcode-dev/workspaces/nomadcode/branches/develop" +git fetch -q origin develop +git log --oneline -1 origin/develop +git diff-tree --no-commit-id --name-status -r origin/develop +``` + +4. Forgejo provider hook delivery가 성공했는지 확인한다. + +```sh +docker exec -i forgejo-db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At' <<'SQL' +select id, hook_id, uuid, event_type, is_delivered, is_succeed, left(request_content, 400), left(response_content, 220) +from hook_task +order by id desc +limit 5; +SQL +``` + +기대값은 현재 `AFTER_SHA`나 milestone path가 들어 있는 push task의 `event_type=push`, `is_delivered=t`, `is_succeed=t`, response status `202`다. 동시 push가 있을 수 있으므로 단순 최신 행이 아니라 현재 테스트의 `after` revision 또는 changed file path와 매칭한다. + +5. Gito가 `branch.updated` event를 만들었는지 확인한다. + +```sh +AFTER_SHA="" +tmp=$(mktemp) +curl -fsS http://127.0.0.1:18061/api/events > "$tmp" +python3 - "$tmp" "$AFTER_SHA" <<'PY' +import json, sys +with open(sys.argv[1], encoding="utf-8") as f: + data=json.load(f) +after=sys.argv[2] +for event in data.get("events", []): + rev=event.get("revision") or {} + if rev.get("after") == after: + print("event_id:", event.get("id")) + print("delivery_id:", event.get("delivery_id")) + print("repo_id:", rev.get("repo_id")) + print("branch:", rev.get("branch")) + print("changed_files:", rev.get("changed_files")) +PY +rm -f "$tmp" +``` + +6. Core가 Gito webhook을 받고 Plane projection을 완료했는지 확인한다. + - Core log에 `POST /api/integrations/gito/webhook` status `202`가 있어야 한다. + - 같은 revision에서 대상 work item의 `gito creation sync enqueued`와 `roadmap creation sync completed`가 이어져야 한다. + - Core task result의 `next_step`은 `plane_todo_moved`, `authoring_run_state`는 `succeeded`여야 한다. +7. Plane API 또는 UI에서 work item state가 `Todo`인지 확인한다. + +```sh +cd "$HOME/agent-work/nomadcode-dev" +set -a; source .env.plane.local; set +a +python3 - <<'PY' +import os, json, ssl, urllib.request +wid="" +base=os.environ["PLANE_BASE_URL"].rstrip("/") +ws=os.environ.get("PLANE_WORKSPACE_SLUG", "general") +project=os.environ["PLANE_PROJECT_ID"] +token=os.environ["PLANE_TOKEN"] +todo=os.environ.get("PLANE_TODO_STATE_ID") +ctx=ssl._create_unverified_context() +req=urllib.request.Request( + f"{base}/api/v1/workspaces/{ws}/projects/{project}/work-items/{wid}", + headers={"X-Api-Key": token, "Accept": "application/json"}, +) +with urllib.request.urlopen(req, context=ctx, timeout=20) as r: + item=json.load(r) +state_id=item.get("state") or item.get("state_id") +print(json.dumps({"work_item_id": wid, "state_id": state_id, "matches_todo_env": state_id == todo}, ensure_ascii=False)) +PY +``` + ## 검증 항목 | 항목 | 증거 유형 | 단계 | @@ -95,6 +226,7 @@ for t in d.get('tasks', []): | `webhook-auto-enqueue`: task가 별도 API 호출 없이 authoring queue 진입 | Core log 또는 status=running | 6 | | `live-creation-projection`: develop 스캔 → ledger steps → Plane Todo | Core log + Plane UI | 7→8 | | slot 재사용: 두 번째 smoke에서 slot available | API 응답 | 9 | +| `forgejo-auto-delivery`: Forgejo hook task success + Gito branch.updated event | Forgejo DB + Gito API | dev 4→5 | ## 완료 오인 방지 기준 @@ -104,8 +236,72 @@ for t in d.get('tasks', []): - IOP queue timeout/stale: IOP `/v1/responses` 장시간 대기, queue timeout, stale monitor는 각각 task metadata에 관찰 가능한 상태로 남아야 한다. authoring 시작이나 단일 실패 로그만으로 terminal 완료/실패를 단정하지 않는다. - push-only 누락: authoring 응답 성공은 완료 근거가 아니다. push-only 요청, remote `develop` revision 확인, Gito wakeup 이후 reconcile evidence가 이어져야 다음 단계 완료로 본다. - Gito delivery-only 성공: signed webhook 202 또는 duplicate 202는 delivery 증거일 뿐 full-cycle 완료 근거가 아니다. active Milestone slug reconcile enqueue, `roadmap_sync_steps`, Plane body/title/comment projection, `Todo` 이동까지 확인한다. +- Forgejo 자동 경로 누락: Gito `/callbacks/forgejo/push`에 직접 POST해서 만든 event는 진단/복구 근거다. Forgejo hook task의 성공 delivery 또는 Forgejo UI/API redelivery 성공이 없으면 자동 Forgejo -> Gito 경로 통과 근거로 쓰지 않는다. +- allowlist 복구 후 재검증: `webhook can only call allowed HTTP servers`가 관찰된 run은 환경 문제 발견 근거로만 남긴다. allowlist 수정 후 새 Plane work item으로 한 번 더 자동 경로를 통과시켜야 한다. - full-cycle 완료: 새 Plane `Backlog + AGENT assignee` authoring roundtrip과 Git에만 있는 active Milestone missing-create/reconcile roundtrip을 모두 secret redaction 상태로 남긴다. +## dev profile 트러블슈팅 + +### git push 후 `develop_match` 대기 + +증상: + +- Core task metadata에 `push_request_state=succeeded`가 있는데 task가 `wait_type=develop_match` 또는 running 상태로 오래 남는다. +- Gito `/api/events`에 새 `after` SHA가 없다. + +확인: + +```sh +docker exec -i forgejo-db sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At' <<'SQL' +select id, uuid, event_type, is_delivered, is_succeed, left(request_content, 400), left(response_content, 300) +from hook_task +order by id desc +limit 5; +SQL +``` + +`response_content`에 `webhook can only call allowed HTTP servers`가 있으면 Forgejo allowlist 문제다. + +복구: + +```sh +cfg="$HOME/docker/services/forgejo/data/volume/gitea/conf/app.ini" +cp "$cfg" "$cfg.bak-$(date +%Y%m%d%H%M%S)" +# [webhook] ALLOWED_HOST_LIST에 host.docker.internal을 추가한다. +grep -n "^ALLOWED_HOST_LIST" "$cfg" +cd "$HOME/docker/services/forgejo/compose" +docker compose restart forgejo || docker compose up -d --force-recreate forgejo +docker inspect forgejo --format '{{.State.Health.Status}}' +docker exec forgejo curl -fsS --max-time 5 http://host.docker.internal:18061/healthz +``` + +복구 후에는 기존 run을 수동 redelivery로 닫을 수 있지만, 자동 경로 판정은 새 Plane work item으로 다시 수행한다. + +### Gito watch 또는 subscription 누락 + +증상: + +- Forgejo hook task는 `202`를 받았는데 Gito `/api/events`에 event가 없다. +- `curl http://127.0.0.1:18061/api/listeners/branches` 또는 `/api/webhook-subscriptions`가 빈 목록이다. + +복구: + +- Gito 제공 계약의 branch watch와 webhook subscription bootstrap을 따른다. +- dev target은 `http://127.0.0.1:18011/api/integrations/gito/webhook`이다. +- signing secret 값은 직접 쓰지 말고 `secret_ref` 또는 Gito runtime의 env resolver를 사용한다. + +### Plane Todo 미전환 + +증상: + +- Forgejo hook task 성공, Gito event 생성, Core Gito webhook `202`까지 확인되었지만 Plane state가 Todo가 아니다. + +확인: + +- Core log에서 대상 work item의 `gito creation sync enqueued`, `roadmap creation sync completed`, `action`, `reason`을 확인한다. +- 생성된 milestone의 provider identity와 filename slug가 Plane work item과 맞는지 확인한다. +- Core task result의 `next_step=plane_todo_moved`가 없으면 완료로 보지 않는다. + ## 대체 근거 (런타임 에퀄리번트) reviewed commit을 현재 실행 중인 서버에 배포하지 않은 경우 아래 단계로 대체 근거를 수집한다. diff --git a/agent-test/plane-dev.md b/agent-test/plane-dev.md index 01cba4a..3545845 100644 --- a/agent-test/plane-dev.md +++ b/agent-test/plane-dev.md @@ -37,10 +37,43 @@ webhook dispatch trigger gate에 주입하는 non-secret env. secret(`PLANE_WEBH ## Webhook Smoke Baseline - Webhook URL: `http://toki-labs.com:18010/api/integrations/plane/webhook` +- Dev profile Core endpoint on the remote host: `http://127.0.0.1:18011/api/integrations/plane/webhook` - Webhook secret storage: SOPS encrypted `secrets/nomadcode.dev.sops.yaml` key `PLANE_WEBHOOK_SECRET` - 2026-06-14 webhook-only smoke used existing Backlog items `NOMAD-10` and `NOMAD-11` by assigning AGENT. - 2026-06-15 live smoke used new Backlog item `93b677f5-7913-4111-ae05-916033ae533b` (WEBHOOK_SMOKE-test-1781525263). Webhook delivery succeeded (202 Accepted) and triggered authoring run with Core Task ID `e3c92411-4056-4b76-a879-66f7904125d0`. +## Dev Full-Cycle Baseline + +2026-07-03 dev profile에서 Plane-origin milestone 생성 roundtrip을 확인했다. 이 섹션은 non-secret evidence만 기록한다. + +### Primary automatic proof + +- Plane work item: `982c14b6-b57f-4de9-8773-bcf00fe1d25a` +- Created title marker: `LIVE-SYNC post-allowlist auto smoke 1783057173` +- Core task: `89e40ba4-efbd-4254-b873-48e54e33ade0` +- git `origin/develop`: `42b0d1a8a5790a6fb93f84cf04925d06a0290ce7` +- commit summary: `42b0d1a docs(roadmap): post allowlist smoke 마일스톤을 추가한다` +- added milestone path: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/post-allowlist-automatic-plane-git-gito-plane-smoke-1783057173.md` +- Forgejo provider delivery: `c77f67fe-3e18-484e-b292-9813e1c741e4`, hook task `is_succeed=t`, Gito callback status `202` +- Gito event: `event-de33bb1fc267c84a`, type `branch.updated`, repo `nomadcode`, branch `develop` +- Plane final state: `Todo` (`45ba7449-f684-4381-af6d-5854747c5e8d`) + +### Diagnostic run + +- Plane work item: `67706758-9555-456b-b051-547fa6b4f093` +- git `origin/develop`: `738f4b8d202e9254f7c4c8fe91bb6f5e729100f0` +- This run exposed a dev Forgejo allowlist issue before the automatic provider delivery path was fixed. +- The run was completed after Gito callback redelivery, so it is evidence for Core/Gito/Plane projection recovery but not primary proof of automatic Forgejo -> Gito delivery. + +## Agent Usage Notes + +- 새 full-cycle test item은 `Backlog` state와 AGENT assignee로 만든다. +- 제목/본문에는 timestamp marker를 넣어 git commit, milestone slug, Plane item을 쉽게 대조할 수 있게 한다. +- 요청 범위는 roadmap milestone 문서처럼 작은 산출물로 제한하고, 기능 코드 구현이나 contract 변경을 요구하지 않는다. +- full-cycle 통과 판정은 `agent-test/live-cycle-smoke.md`의 dev profile 자동 경로 체크리스트를 따른다. +- `Todo` 이동 확인 전에는 Plane item이 완료됐다고 보고하지 않는다. +- raw API token, webhook secret, provider credential, private key는 이 문서나 최종 응답에 기록하지 않는다. + ## Webhook Payload Fixtures `services/core/internal/http/testdata/` 아래 fixture로 payload shape를 고정한다. 모든 fixture는 PII/secret redaction 상태이며 work item id는 placeholder다. @@ -48,4 +81,3 @@ webhook dispatch trigger gate에 주입하는 non-secret env. secret(`PLANE_WEBH - `plane_webhook_issue_assignees_updated.json`: live dev Plane assignee-update 로그 기반 redacted 샘플 (`event=issue`, `action=updated`, `activity.field=assignees`). - `plane_webhook_issue_created.json`: create 후보. live create 로그를 따로 확보하지 않아 관찰된 issue envelope 기반 synthetic candidate다 (`event=issue`, `action=created`, Backlog state). - `plane_webhook_issue_state_updated.json`: state-change 후보. synthetic candidate다 (`event=issue`, `action=updated`, `activity.field=state`, Backlog -> Todo). -