chore: sync workspace changes and add new artifacts
This commit is contained in:
parent
61ac2d7b5f
commit
d80abe8357
21 changed files with 572 additions and 41 deletions
12
README.md
12
README.md
|
|
@ -83,11 +83,11 @@ flutter run
|
|||
|
||||
| 표면 | 현재 compatibility baseline | workspace 기준 | 비고 |
|
||||
|------|-----------------------------|----------------|------|
|
||||
| Flutter web/code-server preview | local smoke에서 빈 포트를 고르고 `/proxy/<port>/` base href를 사용합니다. 기존 예시는 `8081`입니다. | 안정적인 workspace preview는 `13010`부터 사용하고 병렬 preview는 `13011`, `13012`처럼 증가시킵니다. | code-server가 proxy prefix를 유지하거나 제거해도 정적 리소스가 열려야 합니다. |
|
||||
| Flutter web/code-server preview | local smoke에서 빈 포트를 고르고 `/proxy/<port>/` base href를 사용합니다. 기존 예시는 `8081`입니다. | remote local profile은 `13010`, remote dev profile은 `13011`입니다. 병렬 임시 preview는 이후 빈 포트를 고릅니다. | code-server가 proxy prefix를 유지하거나 제거해도 정적 리소스가 열려야 합니다. |
|
||||
| code-server workspace entry | client mock workspace URL은 `http://localhost:8080/?folder=...`를 사용합니다. | 유지 | 이 값은 code-server entry URL 호환성이고 Flutter preview 포트가 아닙니다. |
|
||||
| Core HTTP/API | process/container 내부 기본값은 `HTTP_ADDR=:8080`, Docker `EXPOSE 8080`, local curl 예시는 `localhost:8080`입니다. | compose host publish 기본값은 `18010:8080`입니다. | 기존 `8080` local default는 migration compatibility로 유지하고, compose에서 필요하면 `NOMADCODE_CORE_HOST_PORT=8080`으로 되돌립니다. |
|
||||
| PostgreSQL | compose와 local script는 `code-server-postgres:5432` service DNS를 사용합니다. | host 노출이 꼭 필요하면 `15410:5432`를 후보로 둡니다. | 기본은 service DNS이며 host publish를 추가하지 않습니다. |
|
||||
| Redis | compose와 local script는 `code-server-redis:6379` service DNS를 사용합니다. | host 노출이 꼭 필요하면 `16310:6379`를 후보로 둡니다. | 기본은 service DNS이며 host publish를 추가하지 않습니다. |
|
||||
| Core HTTP/API | process/container 내부 기본값은 `HTTP_ADDR=:8080`, Docker `EXPOSE 8080`, local curl 예시는 `localhost:8080`입니다. | remote local profile은 `18010:8080`, remote dev profile은 `18011:8080`입니다. | 기존 `8080` local default는 migration compatibility로 유지하고, compose에서 필요하면 `NOMADCODE_CORE_HOST_PORT=8080`으로 되돌립니다. |
|
||||
| PostgreSQL | compose와 local script는 `code-server-postgres:5432` service DNS를 사용합니다. | host 노출이 꼭 필요하면 local `15410:5432`, dev `15411:5432`를 후보로 둡니다. | 기본은 service DNS이며 host publish를 추가하지 않습니다. |
|
||||
| Redis | compose와 local script는 `code-server-redis:6379` service DNS를 사용합니다. | host 노출이 꼭 필요하면 local `16310:6379`, dev `16311:6379`를 후보로 둡니다. | 기본은 service DNS이며 host publish를 추가하지 않습니다. |
|
||||
| IOP Edge / provider endpoints | `MODEL_*`, `A2A_*`, `PROTO_SOCKET_*`, `PLANE_*`, `JIRA_*`, `MATTERMOST_*` env로 분리합니다. | public port를 예약하지 않고 각 provider endpoint를 env로 주입합니다. | token, password, API key 원문은 tracked 문서에 쓰지 않고 ignored local env/secret 파일에서만 관리합니다. |
|
||||
|
||||
## 구조
|
||||
|
|
@ -138,14 +138,14 @@ IOP 경계가 걸린 작업은 같은 workspace에 sibling IOP repository가 있
|
|||
| Core | `DATABASE_URL` | 스크립트 사용 시 선택 | Core script는 local 기본값을 제공합니다. 다른 PostgreSQL을 쓰거나 binary를 직접 실행할 때 설정합니다. |
|
||||
| Core | `REDIS_URL`, `REDIS_KEY_PREFIX` | 스크립트 사용 시 선택 | worker/queue 상태에 사용할 Redis 주소와 key prefix입니다. |
|
||||
| Core | `AUTH_USERNAME`, `AUTH_PASSWORD` | 선택 | `AUTH_PASSWORD`를 설정하면 `/readyz`와 `/api/*`에 HTTP Basic Auth가 적용됩니다. |
|
||||
| Core | `NOMADCODE_CORE_HOST_PORT` | 선택 | Docker Compose host publish 포트입니다. 기본값은 remote/local test workspace 기준 `18010`이고 container 내부는 `8080`입니다. |
|
||||
| Core | `NOMADCODE_CORE_HOST_PORT` | 선택 | Docker Compose host publish 포트입니다. remote local 기본값은 `18010`, remote dev 기준은 `18011`이고 container 내부는 `8080`입니다. |
|
||||
| Core | `MODEL_BASE_URL`, `MODEL_API_KEY`, `MODEL_NAME`, `MODEL_CONTEXT_SIZE`, `MODEL_TIMEOUT_SEC` | 선택 | worker execution에서 사용할 IOP Edge OpenAI-compatible Responses endpoint 설정입니다. IOP/NomadCode 전용 실행 문맥은 요청 `metadata`로 전달하는 방향을 기준으로 합니다. |
|
||||
| Core | `A2A_EDGE_URL`, `A2A_AGENT_URL`, `A2A_TOKEN`, `A2A_TIMEOUT_SEC` | 선택 | 향후 A2A-compatible agent delegation 경로 설정입니다. 현재 기본 실행 경로는 아닙니다. |
|
||||
| Core/Client | `PROTO_SOCKET_PATH`, `PROTO_SOCKET_HEARTBEAT_INTERVAL_SEC`, `PROTO_SOCKET_HEARTBEAT_WAIT_SEC`, `PROTO_SOCKET_HOST`, `PROTO_SOCKET_PORT`, `PROTO_SOCKET_SECURE`, `PROTO_SOCKET_ENABLED`, `PROTO_SOCKET_HEARTBEAT_INTERVAL_SECONDS`, `PROTO_SOCKET_HEARTBEAT_WAIT_SECONDS` | 선택 | Core proto-socket endpoint와 Flutter client connector 설정입니다. Client는 host가 없으면 연결을 비활성화합니다. |
|
||||
| Core | `WORKFLOW_TASK_TIMEOUT_SEC` | 선택 | workflow task timeout 기본값을 조정합니다. |
|
||||
| Core | `MATTERMOST_BASE_URL`, `MATTERMOST_TOKEN`, `MATTERMOST_CHANNEL_ID` | 선택 | Mattermost task completion message 발송용 설정입니다. token 값은 문서에 기록하지 않습니다. |
|
||||
| Core | `PLANE_BASE_URL`, `PLANE_TOKEN` | 선택 | Plane work item 조회, comment, state update 연동용 설정입니다. token 값은 문서에 기록하지 않습니다. |
|
||||
| Core | `GITO_WEBHOOK_SECRET`, `GITO_REPO_ID`, `GITO_DEVELOP_REPO_PATH`, `GITO_BRANCH`, `GITO_REMOTE_NAME`, `ROADMAP_CREATION_TODO_STATE_ID`, `PLANE_TODO_STATE_ID`, `GITO_PROTO_SOCKET_URL` | 선택 | Gito signed HTTP webhook `branch.updated` consumer 설정입니다. signing secret, repo id, local develop checkout, Todo state id가 모두 있을 때 HTTP consumer가 켜집니다. `GITO_PROTO_SOCKET_URL`은 이전 proto-socket 호환 경로가 필요할 때만 설정합니다. `ROADMAP_CREATION_TODO_STATE_ID`가 `PLANE_TODO_STATE_ID`보다 우선합니다. |
|
||||
| Core | `GITO_WEBHOOK_SECRET`, `GITO_REPO_ID`, `GITO_DEVELOP_REPO_PATH`, `GITO_BRANCH`, `GITO_REMOTE_NAME`, `ROADMAP_CREATION_TODO_STATE_ID`, `PLANE_TODO_STATE_ID`, `GITO_PROTO_SOCKET_URL` | 선택 | Gito signed HTTP webhook `branch.updated` consumer 설정입니다. signing secret, repo id, local develop checkout, Todo state id가 모두 있을 때 HTTP consumer가 켜집니다. Gito local `18060`은 NomadCode local `18010`으로, Gito dev `18061`은 NomadCode dev `18011`로 delivery합니다. `GITO_PROTO_SOCKET_URL`은 이전 proto-socket 호환 경로가 필요할 때만 설정합니다. `ROADMAP_CREATION_TODO_STATE_ID`가 `PLANE_TODO_STATE_ID`보다 우선합니다. |
|
||||
| Core | `JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_API_TOKEN` | 선택 | Jira issue 조회, comment, status transition 연동용 설정입니다. Jira Cloud Basic auth(`Email:APIToken`) 형식을 사용합니다. |
|
||||
|
||||
## 참고 문서
|
||||
|
|
|
|||
17
agent-contract/index.md
Normal file
17
agent-contract/index.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Agent Contract Index
|
||||
|
||||
## 읽기 규칙
|
||||
|
||||
외부 API, 런타임 호출, 프로젝트 간 연동, 요청/응답 스키마 계약을 확인해야 하는 작업에서만 이 index를 읽는다. 매칭되는 제공/소비 계약만 추가로 읽고, 계약 원문을 README, docs, rules에 복제하지 않는다.
|
||||
|
||||
## 제공 계약
|
||||
|
||||
| id | 읽는 조건 | path |
|
||||
| --- | --- | --- |
|
||||
| 없음 | 없음 | 없음 |
|
||||
|
||||
## 소비 계약
|
||||
|
||||
| id | 읽는 조건 | source |
|
||||
| --- | --- | --- |
|
||||
| `nomadcode.gito-branch-webhook-consumer.v1` | Gito `branch.updated` HTTP webhook 소비, `POST /api/integrations/gito/webhook`, Gito/NomadCode local/dev port profiles `18060/18061` and `18010/18011`, `GITO_WEBHOOK_SECRET`, `GITO_REPO_ID`, `GITO_DEVELOP_REPO_PATH` | `agent-contract/outer/gito-branch-webhook-consumer-v1.md` |
|
||||
109
agent-contract/outer/gito-branch-webhook-consumer-v1.md
Normal file
109
agent-contract/outer/gito-branch-webhook-consumer-v1.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# Contract: nomadcode.gito-branch-webhook-consumer.v1
|
||||
|
||||
## 범위
|
||||
|
||||
이 계약은 NomadCode Core가 Gito의 signed HTTP `branch.updated` webhook delivery를 수신해 develop checkout fetch/scan 및 roadmap creation sync wakeup으로 넘기는 외부 소비 경계를 정의한다.
|
||||
|
||||
원문 제공 계약은 Gito `agent-contract/provided/gito-forgejo-branch-events-v1.md`의 `gito.forgejo-branch-events.v1`이다. 이 문서는 NomadCode가 소비자로서 강제하는 endpoint, headers, enablement, response 의미만 기록한다.
|
||||
|
||||
## 비범위
|
||||
|
||||
- Forgejo/Git provider webhook을 직접 수신하거나 정규화하는 책임은 Gito에 있다.
|
||||
- Gito event 생성, branch watch, webhook subscription 저장/전송 책임은 Gito에 있다.
|
||||
- IOP model execution port와 Responses API 동작은 NomadCode task execution dependency이며 이 webhook 소비 계약의 schema가 아니다.
|
||||
|
||||
## Endpoint
|
||||
|
||||
### `POST /api/integrations/gito/webhook`
|
||||
|
||||
Gito가 delivery하는 `branch.updated` JSON payload를 수신한다. Basic Auth를 우회하는 public callback 경계이며, request body HMAC 서명으로 검증한다.
|
||||
|
||||
Headers:
|
||||
|
||||
| Field | Required | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `X-Gito-Event` | 필수 | 고정값 `branch.updated`. 다른 값은 `400`으로 거부한다. |
|
||||
| `X-Gito-Delivery` | 필수 | stable delivery id. HTTP layer idempotency key로 사용한다. |
|
||||
| `X-Gito-Signature` | 필수 | request body에 대한 HMAC-SHA256 hex digest. `sha256=` prefix와 raw hex를 모두 허용한다. |
|
||||
|
||||
Request body minimum shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "branch.updated",
|
||||
"repo_id": "nomadcode",
|
||||
"branch": "develop",
|
||||
"before": "old-sha",
|
||||
"after": "new-sha",
|
||||
"changed_files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"change_type": "modified"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Accepted payload rules:
|
||||
|
||||
| Field | Required | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `type` | 선택 | 있으면 `branch.updated`여야 한다. |
|
||||
| `repo_id` | 필수 | `GITO_REPO_ID`와 같을 때만 처리 대상이다. |
|
||||
| `branch` | 필수 | `GITO_BRANCH` 또는 기본값 `develop`과 같을 때만 처리 대상이다. |
|
||||
| `before` | 선택 | 이전 revision SHA. 내부 bridge로 전달한다. |
|
||||
| `after` | 선택 | 새 revision SHA. 있으면 revision idempotency key에 포함한다. |
|
||||
| `changed_files` | 선택 | 있으면 object list여야 한다. wakeup 힌트이며 source-of-truth로 단정하지 않는다. |
|
||||
|
||||
Response handling:
|
||||
|
||||
| Status | Body | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `202` | `{"status":"accepted"}` | 대상 repo/branch event를 처리했다. |
|
||||
| `202` | `{"status":"ignored"}` | repo 또는 branch가 target과 달라 side effect 없이 ack했다. |
|
||||
| `202` | `{"status":"duplicate"}` | 같은 delivery id 또는 같은 `repo_id:branch:after` revision을 이미 처리했다. |
|
||||
| `400` | error object | event type, delivery id, JSON, payload shape가 잘못됐다. |
|
||||
| `401` | error object | signature가 없거나 검증에 실패했다. |
|
||||
| `413` | error object | body가 1 MiB 제한을 넘었다. |
|
||||
| `500` | error object | 내부 handler가 실패했다. 재시도 가능하다. |
|
||||
| `503` | error object | consumer enablement 입력이 준비되지 않았다. |
|
||||
|
||||
## Enablement
|
||||
|
||||
NomadCode Core는 아래 값이 모두 있을 때 Gito HTTP webhook consumer를 wiring한다.
|
||||
|
||||
| Env | Required | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `GITO_WEBHOOK_SECRET` | 필수 | `X-Gito-Signature` 검증용 signing secret. raw value는 tracked docs에 쓰지 않는다. |
|
||||
| `GITO_REPO_ID` | 필수 | target repo id. |
|
||||
| `GITO_DEVELOP_REPO_PATH` | 필수 | fetch/scan할 develop checkout path. Core container에서 접근 가능해야 한다. |
|
||||
| `ROADMAP_CREATION_TODO_STATE_ID` 또는 `PLANE_TODO_STATE_ID` | 필수 | Plane-origin roadmap creation sync projection 대상 Todo state id. |
|
||||
| `GITO_BRANCH` | 선택 | 기본값 `develop`. |
|
||||
| `GITO_REMOTE_NAME` | 선택 | 기본값 `origin`. |
|
||||
|
||||
## Environment Port Profiles
|
||||
|
||||
포트 profile은 같은 remote host에서 local용 환경과 dev용 환경을 동시에 분리 운영하기 위한 기본값이다. Endpoint path, headers, body schema는 profile과 무관하게 동일하다.
|
||||
|
||||
| Profile | NomadCode Core URL | Gito source URL | Preview | DB/cache 후보 | Subscription target |
|
||||
| --- | --- | --- | ---: | ---: | --- |
|
||||
| remote local | `http://127.0.0.1:18010` | `http://127.0.0.1:18060` | `13010` | `15410/16310` | `http://127.0.0.1:18010/api/integrations/gito/webhook` |
|
||||
| remote dev | `http://127.0.0.1:18011` | `http://127.0.0.1:18061` | `13011` | `15411/16311` | `http://127.0.0.1:18011/api/integrations/gito/webhook` |
|
||||
|
||||
NomadCode Core container 내부 listen은 `8080`을 유지하고 host publish만 `18010` 또는 `18011`로 분리한다. IOP dev model endpoint `http://toki-labs.com:18083/v1`은 NomadCode 실행 dependency이며 이 계약이 소유하는 port가 아니다.
|
||||
|
||||
## Consumer Responsibilities
|
||||
|
||||
- NomadCode는 `branch.updated`를 wakeup signal로만 받아들인다.
|
||||
- NomadCode는 Plane, agent-roadmap, 기타 source-of-truth mutation 전에 target checkout을 fetch/scan해 실제 revision 상태를 재확인한다.
|
||||
- `changed_files`는 relevance 판단 힌트이며 단독 증거가 아니다.
|
||||
- raw webhook secret, token, provider credential, private endpoint 원문은 tracked 문서나 로그에 기록하지 않는다.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `services/core/internal/http/gito_webhook.go`
|
||||
- `services/core/internal/config/config.go`
|
||||
- `services/core/internal/http/router.go`
|
||||
- `services/core/docker-compose.yml`
|
||||
- `services/core/README.md`
|
||||
- `../gito/agent-contract/provided/gito-forgejo-branch-events-v1.md`
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## 목표
|
||||
|
||||
로드맵 기반 agent-ops 운영 루프와 Plane/Jira 같은 work item provider와 Milestone item의 양방향 동기화 도메인을 Core action 경계로 정리한다. 외부 agent 제어 표면은 현재 확정하지 않고 후속 스케치로 분리한다. 현재는 Roadmap Driven Agent-Ops Automation까지 완료/archive했고, 다음 후보는 Milestone Execution Lifecycle Sync다.
|
||||
로드맵 기반 agent-ops 운영 루프와 Plane/Jira 같은 work item provider와 Milestone item의 양방향 동기화 도메인을 Core action 경계로 정리한다. 외부 agent 제어 표면은 현재 확정하지 않고 후속 스케치로 분리한다. 현재는 Roadmap Driven Agent-Ops Automation까지 완료/archive했고, 다음 후보는 agent-origin roadmap push를 Plane 생성/identity backfill로 수렴시키는 Agent-Origin Milestone Creation Sync다.
|
||||
|
||||
## Milestone 흐름
|
||||
|
||||
|
|
@ -46,9 +46,13 @@
|
|||
- 경로: `agent-roadmap/archive/phase/agent-ops-mcp-control-plane/milestones/roadmap-driven-agent-ops-automation.md`
|
||||
- 요약: roadmap/action core와 Plane/Jira 기반 Milestone item 동기화의 상위 방향과 계약 후보를 정리했고, 코드 레벨 종료 검토 후 archive했다.
|
||||
|
||||
- [계획] Agent-Origin Milestone Creation Sync
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/agent-origin-milestone-creation-sync.md`
|
||||
- 요약: agent 또는 사람이 active Milestone을 직접 수정해 `develop`에 push한 경우 Gito revision 검증 후 Plane work item 생성/갱신, provider identity ledger 기록, identity backfill commit/push, self-loop guard까지 닫는다.
|
||||
|
||||
- [계획] Milestone Execution Lifecycle Sync
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-execution-lifecycle-sync.md`
|
||||
- 요약: Todo 이후 In Progress 실행, 하위 티켓, plan/code-review, 완료/폐기 흐름은 사용자가 명시적으로 해제할 때까지 구현 잠금 상태로 둔다.
|
||||
- 요약: Todo 이후 In Progress 실행, 하위 티켓, plan/code-review, 완료/폐기 흐름은 Agent-Origin Milestone Creation Sync 이후 사용자가 명시적으로 해제할 때까지 구현 잠금 상태로 둔다.
|
||||
|
||||
- [스케치] MCP Control Surface Sketch
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/mcp-control-surface-sketch.md`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
# Milestone: Agent-Origin Milestone Creation Sync
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
agent 또는 사람이 `agent-roadmap` active Milestone을 직접 수정해 `develop`에 push한 경우에도 NomadCode가 Gito branch revision을 검증한 뒤 Plane work item을 생성하거나 갱신하고, 생성된 provider identity를 다시 roadmap 파일에 backfill commit으로 남긴다. Plane-origin 흐름과 같은 identity ledger, revision guard, retry/idempotency 기준을 사용해 양방향 생성 동기화의 빈 경로를 닫는다.
|
||||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- SDD: 필요
|
||||
- SDD 문서: `agent-roadmap/sdd/agent-ops-mcp-control-plane/agent-origin-milestone-creation-sync/SDD.md`
|
||||
- SDD 사유: Gito webhook, Plane provider 쓰기, git identity backfill push, revision/idempotency/identity map을 함께 다루는 sync state machine이다.
|
||||
- 잠금 해제 조건:
|
||||
- [x] SDD 잠금이 해제되어 있다
|
||||
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다
|
||||
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다
|
||||
- [x] Evidence Map이 완료 시 Roadmap Completion과 최종 검증 evidence로 검증 가능하게 연결되어 있다
|
||||
- 결정 필요: 없음
|
||||
|
||||
## 범위
|
||||
|
||||
- Gito `branch.updated` webhook을 wakeup으로 받아 `develop` branch의 target revision을 fetch/scan하는 agent-origin sync 경로
|
||||
- active Milestone 파일 중 provider identity가 없는 신규/변경 Milestone을 Plane work item 생성 대상으로 판별하는 기준
|
||||
- Plane work item 생성 후 `roadmap_sync_identities` ledger에 canonical filename slug와 provider work item id를 저장하는 흐름
|
||||
- 생성된 provider identity block을 원래 Milestone markdown에 backfill하고 별도 commit/push로 `develop`에 반영하는 흐름
|
||||
- Core가 만든 identity-only backfill commit이 다시 들어왔을 때 self-loop와 중복 provider mutation을 막는 idempotency guard
|
||||
- 기존 provider identity가 있는 Milestone 변경은 Plane 본문/제목 갱신으로 수렴하고, identity 충돌/rename/reuse는 conflict로 멈추는 정책
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [agent-origin-sync] Agent-origin Milestone creation sync
|
||||
|
||||
agent-origin roadmap push를 Plane work item 생성/갱신과 provider identity backfill까지 수렴시킨다.
|
||||
|
||||
- [ ] [scan-diff] Gito webhook의 `before..after` 변경 목록을 wakeup 힌트로 사용하되, `after` SHA를 fetch/검증한 뒤 active Milestone diff와 전체 active index를 함께 스캔한다. 검증: `changed_files`만 신뢰하지 않고 `origin/develop`의 `after` SHA가 일치할 때만 sync job이 enqueue된다.
|
||||
- [ ] [missing-create] provider identity가 없는 active Milestone을 agent-origin 생성 후보로 판별하고 Plane work item을 생성한다. 검증: 같은 milestone slug와 revision을 재처리해도 중복 Plane work item을 만들지 않는다.
|
||||
- [ ] [identity-ledger] 생성된 Plane work item id를 `roadmap_sync_identities`에 filename slug 기준으로 upsert하고 revision/idempotency conflict를 구분한다. 검증: 기존 slug가 다른 provider work item에 묶여 있으면 provider mutation 없이 conflict로 멈춘다.
|
||||
- [ ] [identity-backfill] Plane 생성 직후 Milestone markdown에 `Provider identity` block을 주입하고 Core-authored backfill commit을 `develop`에 push한다. 검증: backfill commit에는 identity block 외의 unrelated roadmap 변경이 포함되지 않는다.
|
||||
- [ ] [self-loop] Core-authored identity backfill webhook은 provider 생성/갱신을 반복하지 않고 ledger와 markdown identity 일치 여부만 확인한다. 검증: backfill commit 재전달과 Core 재시작 후 재전달 모두 no-op 또는 complete로 수렴한다.
|
||||
- [ ] [identity-update] provider identity가 이미 있는 Milestone의 agent-origin 본문/제목 변경은 Plane 본문/제목 갱신으로 수렴한다. 검증: `develop` revision guard와 completed-step ledger로 부분 실패를 재시도할 수 있다.
|
||||
- [ ] [conflict-policy] rename, filename slug reuse, provider identity mismatch, stale Plane work item, duplicate active slug를 안전하게 분류한다. 검증: destructive overwrite 없이 conflict/not-ready/skip 결과와 로그가 구분된다.
|
||||
- [ ] [field-smoke] dev 환경에서 agent가 active Milestone을 직접 추가해 push한 뒤 Plane ticket 생성, identity backfill commit, 재전달 no-op까지 확인한다. 검증: secret을 남기지 않은 DB/log/Plane/git evidence가 `complete.log`에 기록된다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 없음
|
||||
- 검토 항목:
|
||||
- [ ] 모든 기능 Task와 검증이 `Roadmap Completion` evidence로 충족되었다
|
||||
- [ ] SDD Evidence Map이 최종 검증 evidence와 일치한다
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- agent-ui 상태 반영: 해당 없음
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- Plane `Todo -> In Progress` 이후 실행 lifecycle과 하위 티켓 생성
|
||||
- agent-task plan/code-review 결과를 Plane 하위 티켓 상태로 동기화하는 흐름
|
||||
- MCP control surface와 외부 agent tool permission 정책
|
||||
- 사용자 승인 없는 자동 archive 이동
|
||||
- provider native API schema 일반화 또는 Jira 구현
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `services/core/internal/gitosync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`, `services/core/internal/authoring/`, `services/core/internal/workitem/`, `services/core/internal/scheduler/`, `agent-contract/outer/gito-branch-webhook-consumer-v1.md`, `agent-roadmap/`
|
||||
- 표준선: Gito webhook은 wakeup signal이며, Plane/roadmap mutation 전에는 반드시 `origin/develop`의 target SHA를 fetch/scan해 검증한다. filename slug가 canonical milestone id이고, provider identity block은 repo portable anchor로 유지한다.
|
||||
- 선행 작업: Plane-Origin Authoring Roundtrip Sync, Roadmap Driven Agent-Ops Automation
|
||||
- 후속 작업: Milestone Execution Lifecycle Sync
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -61,8 +61,8 @@ Todo 이후 실행 lifecycle을 Plane 하위 티켓, Milestone Task, agent-task
|
|||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `services/core/internal/workitem/`, `services/core/internal/scheduler/`, `services/core/internal/workflow/`, `agent-task/`, `packages/contracts/`, `agent-roadmap/`
|
||||
- 선행 작업: `Roadmap Driven Agent-Ops Automation`
|
||||
- 선행 작업: `Roadmap Driven Agent-Ops Automation`, `Agent-Origin Milestone Creation Sync`
|
||||
- 잠금 해제 조건: 사용자가 명시적으로 이 마일스톤의 잠금 해제를 요청한다.
|
||||
- 현재 지점: 사용자 확인과 명시적 잠금 해제 전까지 실행하지 않는 후속 마일스톤이다.
|
||||
- 현재 지점: agent-origin 생성 동기화가 닫히고 사용자가 명시적으로 잠금 해제를 요청하기 전까지 실행하지 않는 후속 마일스톤이다.
|
||||
- 확인 필요:
|
||||
- [ ] 잠금 해제 후 `Todo -> In Progress` 이후 범위를 다시 확인한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
# SDD: Agent-Origin Milestone Creation Sync
|
||||
|
||||
## 위치
|
||||
|
||||
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/agent-origin-milestone-creation-sync.md`
|
||||
- Phase: `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
|
||||
|
||||
## 상태
|
||||
|
||||
[승인됨]
|
||||
|
||||
## SDD 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- 사용자 리뷰: 없음
|
||||
- 잠금 항목:
|
||||
- 없음
|
||||
|
||||
## 문제 / 비목표
|
||||
|
||||
- 문제: Plane-origin 흐름은 Plane 요청에서 시작해 pushed develop SHA를 확인한 뒤 Plane Todo projection까지 수렴하지만, agent 또는 사람이 `agent-roadmap` active Milestone을 직접 수정해 push한 경우 provider identity가 없는 Milestone을 Plane work item과 안정적으로 연결하고 그 identity를 repo에 되돌려 쓰는 경로가 부족하다.
|
||||
- 비목표:
|
||||
- Todo 이후 실행 lifecycle과 Plane 하위 티켓 동기화
|
||||
- MCP control surface 또는 외부 agent tool permission 정책
|
||||
- provider native API schema 일반화 또는 Jira 구현
|
||||
- 사용자 승인 없는 archive 이동
|
||||
|
||||
## Source of Truth
|
||||
|
||||
| 영역 | 기준 | 메모 |
|
||||
|------|------|------|
|
||||
| Roadmap | `agent-roadmap/phase/**/milestones/*.md` on `develop` | active Milestone markdown이 Plane projection의 본문/제목 기준이며 filename slug가 canonical milestone id다. |
|
||||
| Code | `services/core/internal/gitosync/`, `services/core/internal/roadmapsyncpipeline/`, `services/core/internal/roadmapsync/` | Gito revision scan, identity matching, provider mutation, ledger/retry orchestration을 소유한다. |
|
||||
| External Provider | Plane | Plane work item id와 state는 provider side effect 결과이며 raw token/secret은 tracked 문서에 기록하지 않는다. |
|
||||
| User Decision | 없음 | 사용자는 agent-origin identity backfill 방향을 승인했고, 남은 항목은 기존 sync/domain rule로 구현 가능하다. |
|
||||
|
||||
## State Machine
|
||||
|
||||
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|
||||
|------|-----------|-----------|------|
|
||||
| `branch_event_received` | Gito `branch.updated` webhook이 target repo/branch로 들어온다. | `revision_verified` 또는 `not_ready` | `agent-contract/outer/gito-branch-webhook-consumer-v1.md` |
|
||||
| `revision_verified` | local develop checkout fetch 후 `origin/develop`이 event `after` SHA와 일치한다. | `milestone_scanned` | `git fetch`, `git rev-parse`, `git ls-tree`, `git show` |
|
||||
| `milestone_scanned` | active Milestone diff와 active index를 읽고 identity 유무를 분류한다. | `missing_identity_candidate`, `identity_matched`, `conflict` | roadmap parser, duplicate slug guard |
|
||||
| `missing_identity_candidate` | provider identity가 없는 active Milestone이 생성 후보로 판별된다. | `plane_created` 또는 `conflict` | filename slug, project sync config, ledger lookup |
|
||||
| `plane_created` | Plane work item 생성과 DB identity upsert가 성공한다. | `identity_backfilled` | Plane adapter, `roadmap_sync_identities` |
|
||||
| `identity_backfilled` | Milestone markdown에 `Provider identity` block을 주입한 commit이 `develop`에 push된다. | `self_loop_checked` | Core-authored git commit/push |
|
||||
| `identity_matched` | provider identity가 있는 Milestone 변경이다. | `plane_projected`, `complete`, `conflict` | roadmap revision guard, completed-step ledger |
|
||||
| `self_loop_checked` | identity-only backfill commit webhook이 재수신된다. | `complete` 또는 `conflict` | actor/commit marker, markdown identity, ledger |
|
||||
| `conflict` | slug reuse, identity mismatch, duplicate active slug, destructive overwrite 위험이 감지된다. | `conflict` | no provider mutation, operator/user review evidence |
|
||||
|
||||
## Interface Contract
|
||||
|
||||
- 계약 원문: `agent-contract/outer/gito-branch-webhook-consumer-v1.md`
|
||||
- 입력:
|
||||
- `repo_id`: target repository id. `GITO_REPO_ID`와 일치해야 한다.
|
||||
- `branch`: target branch. 기본값은 `develop`이다.
|
||||
- `before`: 이전 git SHA. diff 후보 산정에 사용하되 source-of-truth로 단정하지 않는다.
|
||||
- `after`: 새 git SHA. fetch/scan 및 idempotency 기준이다.
|
||||
- `changed_files`: wakeup relevance 힌트. 실제 sync는 target revision scan으로 확인한다.
|
||||
- 출력:
|
||||
- Plane work item 생성/갱신 결과
|
||||
- `roadmap_sync_identities` identity row와 completed step ledger
|
||||
- provider identity block backfill commit SHA
|
||||
- conflict/not-ready/complete/projected 로그와 job result
|
||||
- 금지:
|
||||
- `changed_files`만 믿고 Plane 또는 roadmap 파일을 mutation하지 않는다.
|
||||
- provider identity가 충돌하는 Milestone을 덮어쓰지 않는다.
|
||||
- Core-authored backfill commit 재전달로 Plane work item을 중복 생성하지 않는다.
|
||||
- raw secret, token, provider credential을 tracked evidence나 로그에 남기지 않는다.
|
||||
|
||||
## Acceptance Scenarios
|
||||
|
||||
| ID | Milestone Task | Given | When | Then |
|
||||
|----|----------------|-------|------|------|
|
||||
| S01 | `scan-diff` | Gito event가 `before`, `after`, `changed_files`를 포함한다. | Core가 fetch 후 `origin/develop`과 `after` SHA를 비교한다. | SHA가 일치할 때만 active Milestone scan이 진행되고, 불일치하면 not-ready로 남는다. |
|
||||
| S02 | `missing-create` | active Milestone 파일이 추가됐고 provider identity가 없다. | `after` revision scan에서 생성 후보로 분류된다. | Plane work item이 1개만 생성되고 같은 revision 재처리는 중복 생성하지 않는다. |
|
||||
| S03 | `identity-ledger` | 생성된 Plane work item id와 milestone slug가 있다. | identity row를 upsert한다. | 같은 slug/provider key는 재시도 가능하고, 다른 provider work item과 충돌하면 conflict로 멈춘다. |
|
||||
| S04 | `identity-backfill` | Plane 생성과 identity ledger 기록이 완료됐다. | Core가 Milestone markdown에 provider identity block을 추가한다. | identity-only backfill commit이 `develop`에 push되고 unrelated 변경은 포함되지 않는다. |
|
||||
| S05 | `self-loop` | backfill commit으로 다시 Gito webhook이 들어온다. | Core가 actor/commit marker, markdown identity, ledger를 비교한다. | Plane 생성/갱신이 반복되지 않고 complete/no-op으로 수렴한다. |
|
||||
| S06 | `identity-update` | provider identity가 있는 Milestone markdown이 agent-origin으로 수정됐다. | Core가 target revision을 scan한다. | Plane 제목/본문 갱신이 ledger step으로 재시도 가능하게 수행된다. |
|
||||
| S07 | `conflict-policy` | rename, slug reuse, stale provider item, duplicate active slug 중 하나가 발생한다. | Core가 sync를 시도한다. | destructive overwrite 없이 conflict/not-ready/skip 중 하나로 분류하고 provider mutation을 제한한다. |
|
||||
| S08 | `field-smoke` | dev Plane/Gito/NomadCode/IOP 환경이 준비되어 있다. | agent가 identity 없는 active Milestone을 직접 push한다. | Plane ticket 생성, identity backfill commit, 재전달 no-op, secret-redacted evidence가 확인된다. |
|
||||
|
||||
## Evidence Map
|
||||
|
||||
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|
||||
|----------|-------------------|------------------|---------------------------|
|
||||
| S01 | gitosync scanner/bridge unit test and webhook handler test | `agent-task/m-agent-origin-milestone-creation-sync/...` | `scan-diff` Roadmap Completion, test output, revision mismatch/not-ready evidence |
|
||||
| S02 | roadmapsyncpipeline missing-create idempotency test | `agent-task/m-agent-origin-milestone-creation-sync/...` | `missing-create` Roadmap Completion, duplicate retry evidence |
|
||||
| S03 | storage identity conflict/upsert test | `agent-task/m-agent-origin-milestone-creation-sync/...` | `identity-ledger` Roadmap Completion, conflict evidence |
|
||||
| S04 | identity backfill writer test and git commit scope check | `agent-task/m-agent-origin-milestone-creation-sync/...` | `identity-backfill` Roadmap Completion, commit diff evidence |
|
||||
| S05 | self-loop/redelivery test across process restart or durable ledger | `agent-task/m-agent-origin-milestone-creation-sync/...` | `self-loop` Roadmap Completion, no-op/complete evidence |
|
||||
| S06 | provider identity update projection test | `agent-task/m-agent-origin-milestone-creation-sync/...` | `identity-update` Roadmap Completion, Plane body/title projection evidence |
|
||||
| S07 | conflict classification tests | `agent-task/m-agent-origin-milestone-creation-sync/...` | `conflict-policy` Roadmap Completion, no destructive provider mutation evidence |
|
||||
| S08 | dev field smoke with redacted logs/DB/Plane/git evidence | `agent-task/m-agent-origin-milestone-creation-sync/...` | `field-smoke` Roadmap Completion, final dev cycle evidence without secrets |
|
||||
|
||||
## Cross-repo Dependencies
|
||||
|
||||
- Gito provides `gito.forgejo-branch-events.v1`; NomadCode consumes it through `agent-contract/outer/gito-branch-webhook-consumer-v1.md`.
|
||||
|
||||
## Drift Check
|
||||
|
||||
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
|
||||
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
|
||||
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
|
||||
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
|
||||
|
||||
## 사용자 리뷰 이력
|
||||
|
||||
- 없음
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 표준선: `develop` branch의 active Milestone markdown이 roadmap source of truth이며, Gito webhook은 wakeup으로만 사용한다. Provider identity block은 repo portable anchor이고, DB ledger는 runtime idempotency/retry state다.
|
||||
- 후속 SDD: Milestone Execution Lifecycle Sync에서 하위 티켓/agent-task lifecycle을 다룰 때 별도 SDD 필요 여부를 다시 판정한다.
|
||||
66
agent-test/dev/rules.md
Normal file
66
agent-test/dev/rules.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
test_env: dev
|
||||
last_rule_updated_at: 2026-07-01
|
||||
---
|
||||
|
||||
# dev 테스트 규칙
|
||||
|
||||
**현재 문서를 반드시 끝까지 정독하고 작업한다. 다 읽지 않고 즉각 작업은 금지한다.**
|
||||
**dev 환경은 remote local profile과 같은 host에서 분리 운영하는 NomadCode/Gito 연동 검증용 profile이다.**
|
||||
|
||||
## 공통 규칙
|
||||
|
||||
- dev 테스트/검증은 이 파일을 기준으로 판단한다.
|
||||
- 원격 host에서 local profile 포트와 dev profile 포트를 동시에 띄울 수 있어야 한다.
|
||||
- 환경값, secret, private endpoint 원문은 tracked docs/roadmap/task log/최종 응답에 기록하지 않는다.
|
||||
- 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` 대역을 사용하지 않는다.
|
||||
|
||||
## 기본 환경
|
||||
|
||||
- host: standard remote runner `toki@toki-labs.com`.
|
||||
- checkout: dev profile은 `$HOME/agent-work/nomadcode-dev`를 기준으로 둔다. 아직 없으면 현재 검증 대상 branch/commit으로 생성 또는 동기화한 뒤 사용한다.
|
||||
- runtime: repo root helper(`bin/test`, `bin/lint`, `bin/build`)와 변경 범위별 Go, Flutter/Dart toolchain.
|
||||
- package manager: Go modules, Flutter pub.
|
||||
- docker: Compose는 Core 내부 listen `8080`을 유지하고 host publish를 `NOMADCODE_CORE_HOST_PORT=18011`로 둔다. 별도 DB/cache host publish가 필요할 때만 `15411/16311` 후보를 사용한다.
|
||||
- external service: Mattermost, FCM, Plane, Gito 등 도메인/검증 시나리오별 문서가 명시한 서비스만 사용한다.
|
||||
- model endpoint: 기본 IOP dev dependency는 `http://toki-labs.com:18083/v1`이다. 이 포트는 NomadCode-owned slot이 아니며 IOP dev 환경이 소유한다.
|
||||
- credential: ignored local file only.
|
||||
|
||||
## 포트 매핑
|
||||
|
||||
| 용도 | local profile | dev profile |
|
||||
| --- | ---: | ---: |
|
||||
| Flutter/code-server preview | `13010` | `13011` |
|
||||
| NomadCode Core host publish | `18010:8080` | `18011:8080` |
|
||||
| PostgreSQL host publish 후보 | `15410` | `15411` |
|
||||
| Redis host publish 후보 | `16310` | `16311` |
|
||||
| Gito delivery source | `18060` | `18061` |
|
||||
|
||||
dev NomadCode의 Gito webhook target은 `http://127.0.0.1:18011/api/integrations/gito/webhook`이다. local profile target은 `http://127.0.0.1:18010/api/integrations/gito/webhook`으로 유지한다.
|
||||
|
||||
## Gito Webhook Consumer 기준
|
||||
|
||||
- consumer endpoint는 `POST /api/integrations/gito/webhook`이다.
|
||||
- dev Gito base URL은 `http://127.0.0.1:18061`이고 subscription target은 `http://127.0.0.1:18011/api/integrations/gito/webhook`이다.
|
||||
- consumer enablement에는 `GITO_WEBHOOK_SECRET`, `GITO_REPO_ID`, `GITO_DEVELOP_REPO_PATH`, `ROADMAP_CREATION_TODO_STATE_ID` 또는 `PLANE_TODO_STATE_ID`가 필요하다.
|
||||
- `GITO_BRANCH` 기본값은 `develop`, `GITO_REMOTE_NAME` 기본값은 `origin`이다.
|
||||
- `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`를 함께 따른다.
|
||||
|
||||
## 라우팅
|
||||
|
||||
- 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`
|
||||
- mobile / smoke / Mattermost server-generated signed push, Nexo parity, Android remote push evidence: `agent-test/local/mattermost-server-generated-push-smoke.md`
|
||||
- contracts / smoke / packages/contracts contract docs/schema baseline: `agent-test/local/contracts-smoke.md`
|
||||
- workspace-ops / smoke / root helpers, docs, agent rules, roadmap baseline: `agent-test/local/workspace-ops-smoke.md`
|
||||
|
||||
## 라우팅 규칙
|
||||
|
||||
- 여러 항목이 맞으면 모두 읽는다.
|
||||
- 도메인 매핑이나 domain rule이 있으면 각 도메인의 `<domain>-smoke` 문서를 기본 baseline으로 둔다.
|
||||
- 도메인이 아직 없을 때만 `project-smoke`를 fallback baseline으로 둔다.
|
||||
- 도메인/검증 시나리오별 문서는 다른 테스트 문서로 라우팅하지 않는다.
|
||||
|
|
@ -93,11 +93,11 @@
|
|||
|
||||
| 표면 | 후보 기준 | compatibility note |
|
||||
|------|-----------|--------------------|
|
||||
| Flutter web preview | code-server `/proxy/<port>/` 아래 `13010+` host port 대역 | 기존 local smoke의 ad hoc `8081` 계열 포트는 계속 허용한다. |
|
||||
| Flutter web preview | code-server `/proxy/<port>/` 아래 remote local `13010`, remote dev `13011` host port | 기존 local smoke의 ad hoc `8081` 계열 포트는 계속 허용한다. |
|
||||
| code-server workspace entry | `http://localhost:8080/?folder=...` mock URL 유지 | 이 URL은 code-server entry 호환성이고 Flutter web preview 포트가 아니다. |
|
||||
| Core HTTP/API | process/container 내부 `8080`, workspace host publish 기본값 `18010:8080` | `HTTP_ADDR=:8080`, Docker `EXPOSE 8080`, local curl `localhost:8080`은 migration compatibility로 유지하고, compose는 `NOMADCODE_CORE_HOST_PORT`로 host port를 override한다. |
|
||||
| PostgreSQL | compose/service DNS `code-server-postgres:5432`; host 필요 시 `15410:5432` 후보 | 기본 내부 통신은 service DNS를 우선하고 host publish는 추가하지 않는다. |
|
||||
| Redis | compose/service DNS `code-server-redis:6379`; host 필요 시 `16310:6379` 후보 | 기본 내부 통신은 service DNS를 우선하고 host publish는 추가하지 않는다. |
|
||||
| Core HTTP/API | process/container 내부 `8080`, remote local host publish `18010:8080`, remote dev host publish `18011:8080` | `HTTP_ADDR=:8080`, Docker `EXPOSE 8080`, local curl `localhost:8080`은 migration compatibility로 유지하고, compose는 `NOMADCODE_CORE_HOST_PORT`로 host port를 override한다. |
|
||||
| PostgreSQL | compose/service DNS `code-server-postgres:5432`; host 필요 시 local `15410:5432`, dev `15411:5432` 후보 | 기본 내부 통신은 service DNS를 우선하고 host publish는 추가하지 않는다. |
|
||||
| Redis | compose/service DNS `code-server-redis:6379`; host 필요 시 local `16310:6379`, dev `16311:6379` 후보 | 기본 내부 통신은 service DNS를 우선하고 host publish는 추가하지 않는다. |
|
||||
| proto-socket client/core endpoint | client는 `PROTO_SOCKET_*`, core는 `/proto-socket`과 heartbeat env를 사용 | client `PROTO_SOCKET_HOST`가 없으면 connector는 비활성화된다. |
|
||||
| IOP Edge/provider endpoints | `MODEL_*`, `A2A_*`, `PLANE_*`, `JIRA_*`, `MATTERMOST_*` env group | raw token, password, API key 값은 tracked contract note에 기록하지 않는다. |
|
||||
|
||||
|
|
@ -162,17 +162,19 @@
|
|||
- 검증은 최소한 channel/action dispatch, auth boundary, error envelope, event broadcast 또는 reconnect 영향 중 해당되는 항목을 테스트로 고정한다.
|
||||
|
||||
### 1.8 외부 Gito Branch Event Consumer
|
||||
- **설명**: NomadCode는 Gito의 branch event를 proto-socket을 통해 수신하는 소비자입니다. 소비 표면은 Flutter client wakeup과 Core backend bridge 두 책임으로 나뉩니다.
|
||||
- **원문 계약**: Gito `agent-contract/provided/gito-forgejo-branch-events-v1.md` (계약 ID: `gito.forgejo-branch-events.v1`)
|
||||
- **소비 액션**: `event.subscribe` (구독 요청) 및 `branch.updated` (브로드캐스트 이벤트)
|
||||
- **공통 책임 경계**: NomadCode는 `branch.updated` 이벤트를 단순 wakeup signal로만 해석하고 자체적인 Plane/roadmap mutation은 실행하지 않으며, 이벤트 수신 후 target branch revision에 대한 재검증 및 후속 동기화 여부를 직접 확인해야 합니다. 계약 본문 전체는 Gito 원문 계약을 따릅니다.
|
||||
- **설명**: NomadCode는 Gito의 branch event를 HTTP webhook으로 수신하는 소비자입니다. 소비 표면은 Core backend bridge가 canonical이며, Flutter client wakeup은 후속 UI projection 책임으로 분리합니다.
|
||||
- **NomadCode 소비 계약**: `agent-contract/outer/gito-branch-webhook-consumer-v1.md` (계약 ID: `nomadcode.gito-branch-webhook-consumer.v1`)
|
||||
- **Gito 원문 제공 계약**: Gito `agent-contract/provided/gito-forgejo-branch-events-v1.md` (계약 ID: `gito.forgejo-branch-events.v1`)
|
||||
- **소비 표면**: `POST /api/integrations/gito/webhook` 및 `branch.updated` payload.
|
||||
- **포트 profile**: remote local은 Gito `18060` -> NomadCode `18010`, remote dev는 Gito `18061` -> NomadCode `18011`을 기준으로 한다.
|
||||
- **공통 책임 경계**: NomadCode는 `branch.updated` 이벤트를 단순 wakeup signal로만 해석하고, Plane/roadmap mutation 전 target branch revision에 대한 재검증 및 후속 동기화 여부를 직접 확인해야 합니다. `GITO_PROTO_SOCKET_URL`은 이전 wire consumer 호환 경로가 필요할 때만 사용합니다.
|
||||
|
||||
#### 1.8.1 Flutter client wakeup
|
||||
- **소비자**: `apps/client` Flutter 앱.
|
||||
- **용도**: branch event를 UI wakeup 힌트로만 사용합니다. 화면 갱신/알림 트리거에 쓰며, 자체적으로 source-of-truth 상태를 변경하지 않습니다.
|
||||
- **용도**: Core가 처리한 branch event projection을 UI wakeup 힌트로만 사용합니다. 화면 갱신/알림 트리거에 쓰며, 자체적으로 source-of-truth 상태를 변경하지 않습니다.
|
||||
|
||||
#### 1.8.2 Core backend bridge
|
||||
- **소비자**: `services/core` 내부 Gito branch event client (`services/core/internal/gitoevents`).
|
||||
- **소비자**: `services/core` HTTP Gito webhook consumer (`POST /api/integrations/gito/webhook`) 및 내부 Gito branch event bridge.
|
||||
- **용도**: Core는 Gito 제공 계약 `gito.forgejo-branch-events.v1`을 소비해 `branch.updated`를 backend sync enqueue wakeup으로만 사용합니다.
|
||||
- **`changed_files` 취급**: `changed_files`는 roadmap Milestone 관련성 판단을 위한 wakeup 힌트이며, 그 자체를 신뢰해 동기화하지 않습니다. 실제 target branch revision 확인/스캔은 backend bridge(후속 revision-scan 단계)의 책임입니다.
|
||||
- **구독 필터**: `repo_id`, `branch`로 구독하며, 다른 repo/branch event는 enqueue 후보로 넘기지 않습니다.
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Jira 연동은 Jira Cloud Basic auth(`Email:APIToken`) 형식을 사용합니다
|
|||
|
||||
Mattermost task completion notification은 `MATTERMOST_BASE_URL`, `MATTERMOST_TOKEN`, `MATTERMOST_CHANNEL_ID` 환경 변수를 사용해 `POST /api/v4/posts`로 발송합니다. 설정이 누락되거나 Mattermost가 non-2xx를 반환하면 core는 notification projection 오류를 기록하되 canonical task completion 상태를 롤백하지 않습니다.
|
||||
|
||||
Gito HTTP webhook consumer는 `POST /api/integrations/gito/webhook`에서 signed `branch.updated` wakeup을 받고, local develop checkout을 fetch/scan한 뒤 Plane-origin roadmap creation sync job으로 넘깁니다. `GITO_WEBHOOK_SECRET`, `GITO_REPO_ID`, `GITO_DEVELOP_REPO_PATH`, `ROADMAP_CREATION_TODO_STATE_ID` 또는 `PLANE_TODO_STATE_ID`가 모두 있어야 켜지며, `GITO_BRANCH` 기본값은 `develop`, `GITO_REMOTE_NAME` 기본값은 `origin`입니다. `ROADMAP_CREATION_TODO_STATE_ID`를 설정하면 `PLANE_TODO_STATE_ID`보다 우선합니다. Gito 쪽 bootstrap은 watched branch 등록과 webhook subscription 등록을 사용하고, subscription의 `target_url`은 이 Core callback endpoint를 가리키며 signing secret 원문은 tracked 문서에 기록하지 않습니다. `GITO_PROTO_SOCKET_URL`은 이전 wire consumer 호환 경로가 필요할 때만 별도로 설정합니다.
|
||||
Gito HTTP webhook consumer는 `POST /api/integrations/gito/webhook`에서 signed `branch.updated` wakeup을 받고, local develop checkout을 fetch/scan한 뒤 Plane-origin roadmap creation sync job으로 넘깁니다. `GITO_WEBHOOK_SECRET`, `GITO_REPO_ID`, `GITO_DEVELOP_REPO_PATH`, `ROADMAP_CREATION_TODO_STATE_ID` 또는 `PLANE_TODO_STATE_ID`가 모두 있어야 켜지며, `GITO_BRANCH` 기본값은 `develop`, `GITO_REMOTE_NAME` 기본값은 `origin`입니다. `ROADMAP_CREATION_TODO_STATE_ID`를 설정하면 `PLANE_TODO_STATE_ID`보다 우선합니다. Gito 쪽 bootstrap은 watched branch 등록과 webhook subscription 등록을 사용하고, subscription의 `target_url`은 이 Core callback endpoint를 가리키며 signing secret 원문은 tracked 문서에 기록하지 않습니다. remote local target은 `http://127.0.0.1:18010/api/integrations/gito/webhook`, remote dev target은 `http://127.0.0.1:18011/api/integrations/gito/webhook`입니다. 계약 원문은 `agent-contract/outer/gito-branch-webhook-consumer-v1.md`와 Gito 제공 계약 `../gito/agent-contract/provided/gito-forgejo-branch-events-v1.md`를 함께 봅니다. `GITO_PROTO_SOCKET_URL`은 이전 wire consumer 호환 경로가 필요할 때만 별도로 설정합니다.
|
||||
|
||||
```bash
|
||||
GITO_WEBHOOK_SECRET="<gito-webhook-signing-secret>" \
|
||||
|
|
@ -186,13 +186,19 @@ DB 접근 코드는 `migrations/`의 스키마와 `queries/`의 SQL을 기준으
|
|||
|
||||
Docker Compose 실행은 dev 배포용입니다. compose는 외부 Docker 네트워크 `net_nginx`에 붙고, 같은 네트워크의 `code-server-postgres`에 `nomad-core-dev` DB로, `code-server-redis`의 Redis DB 4번과 `nomadcode-core:dev` key prefix로 접속합니다. dev Redis 주소나 prefix를 바꿀 때는 local 실행용 `REDIS_URL`, `REDIS_KEY_PREFIX`와 분리된 `DEV_REDIS_URL`, `DEV_REDIS_KEY_PREFIX`를 사용합니다. 외부 노출 테스트에서는 `AUTH_PASSWORD`를 함께 지정합니다.
|
||||
|
||||
Compose도 Gito HTTP webhook consumer env를 빈 기본값으로 전달하므로, signing secret, repo id, local checkout volume, Todo state id가 준비된 경우에만 consumer가 활성화됩니다. `GITO_DEVELOP_REPO_PATH`는 container에서 접근 가능한 경로여야 합니다.
|
||||
Compose도 Gito HTTP webhook consumer env를 빈 기본값으로 전달하므로, signing secret, repo id, local checkout volume, Todo state id가 준비된 경우에만 consumer가 활성화됩니다. `GITO_DEVELOP_REPO_PATH`는 container에서 접근 가능한 경로여야 합니다. remote local profile은 `NOMADCODE_CORE_HOST_PORT=18010`, remote dev profile은 `NOMADCODE_CORE_HOST_PORT=18011`을 사용하고 Core container 내부 listen은 `8080`으로 유지합니다.
|
||||
|
||||
```bash
|
||||
AUTH_PASSWORD="change-me" ./bin/docker-up
|
||||
```
|
||||
|
||||
Compose의 기본 host publish는 remote/local test workspace 기준 `18010:8080`입니다. 기존 host `8080`이 필요한 호환성 확인에서는 아래처럼 명시합니다.
|
||||
Compose의 기본 host publish는 remote/local test workspace 기준 `18010:8080`입니다. dev profile은 같은 remote host에서 local profile과 분리하기 위해 아래처럼 `18011:8080`을 명시합니다.
|
||||
|
||||
```bash
|
||||
NOMADCODE_CORE_HOST_PORT=18011 AUTH_PASSWORD="change-me" ./bin/docker-up
|
||||
```
|
||||
|
||||
기존 host `8080`이 필요한 호환성 확인에서는 아래처럼 명시합니다.
|
||||
|
||||
```bash
|
||||
NOMADCODE_CORE_HOST_PORT=8080 AUTH_PASSWORD="change-me" ./bin/docker-up
|
||||
|
|
|
|||
|
|
@ -427,6 +427,10 @@ func buildRequestMetadata(input model.GenerateInput) map[string]any {
|
|||
}
|
||||
merged := make(map[string]any, len(input.Metadata)+1)
|
||||
for k, v := range input.Metadata {
|
||||
// IOP Responses contract rejects ambiguous call-origin metadata.
|
||||
if k == "source" {
|
||||
continue
|
||||
}
|
||||
merged[k] = v
|
||||
}
|
||||
if input.WorkspaceMetadata != nil {
|
||||
|
|
@ -434,6 +438,9 @@ func buildRequestMetadata(input model.GenerateInput) map[string]any {
|
|||
merged["workspace"] = path
|
||||
}
|
||||
}
|
||||
if len(merged) == 0 {
|
||||
return nil
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,9 +76,12 @@ func TestGenerateCallsResponsesAPI(t *testing.T) {
|
|||
if !ok {
|
||||
t.Fatalf("expected metadata object, got %#v", gotBody["metadata"])
|
||||
}
|
||||
if metadata["task_id"] != "task-123" || metadata["source"] != "plane" {
|
||||
if metadata["task_id"] != "task-123" {
|
||||
t.Fatalf("unexpected metadata: %#v", metadata)
|
||||
}
|
||||
if _, ok := metadata["source"]; ok {
|
||||
t.Fatalf("metadata.source must not be sent to IOP-compatible responses: %#v", metadata)
|
||||
}
|
||||
if _, ok := gotBody["context_size"]; ok {
|
||||
t.Fatal("Responses API request must not include unsupported context_size")
|
||||
}
|
||||
|
|
@ -131,9 +134,12 @@ func TestGenerateWorkspaceMetadataUsesFlatWorkspacePath(t *testing.T) {
|
|||
if !ok {
|
||||
t.Fatalf("expected metadata object, got %T: %#v", gotBody["metadata"], gotBody["metadata"])
|
||||
}
|
||||
if metadata["task_id"] != "task-abc" || metadata["source"] != "plane" {
|
||||
if metadata["task_id"] != "task-abc" {
|
||||
t.Fatalf("flat metadata not preserved: %#v", metadata)
|
||||
}
|
||||
if _, ok := metadata["source"]; ok {
|
||||
t.Fatalf("metadata.source must not be sent to IOP-compatible responses: %#v", metadata)
|
||||
}
|
||||
wsPath, ok := metadata["workspace"].(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected metadata.workspace string, got %T: %#v", metadata["workspace"], metadata["workspace"])
|
||||
|
|
|
|||
|
|
@ -395,6 +395,9 @@ func (c *Client) do(ctx context.Context, method, path string, input any, output
|
|||
return err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("%w: plane API request failed: status %d: %s", workitem.ErrNotFound, resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||
}
|
||||
return fmt.Errorf("plane API request failed: status %d: %s", resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||
}
|
||||
if output == nil || len(raw) == 0 {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package plane
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
|
@ -132,6 +133,25 @@ func TestClientReturnsResponseBodyOnError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetWorkItemWrapsNotFound(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"error":"missing"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
|
||||
_, err := client.GetWorkItem(context.Background(), WorkItemRef{
|
||||
WorkspaceSlug: "general",
|
||||
ProjectID: "project-1",
|
||||
WorkItemID: "missing",
|
||||
})
|
||||
if !errors.Is(err, workitem.ErrNotFound) {
|
||||
t.Fatalf("GetWorkItem error: got %v, want workitem.ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientImplementsWorkItemFacets(t *testing.T) {
|
||||
client := NewClient(Config{BaseURL: "https://example.com", Token: "test-token"}, nil)
|
||||
if client.ProviderID() != workitem.ProviderID("plane") {
|
||||
|
|
@ -548,8 +568,8 @@ func TestFetchWorkItemMapsStateAndAssignees(t *testing.T) {
|
|||
// Test case 3: assignees as nested objects (e.g. assignee detail)
|
||||
server3 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": "work-3",
|
||||
"name": "Test 3",
|
||||
"id": "work-3",
|
||||
"name": "Test 3",
|
||||
"state_id": "state-direct",
|
||||
"assignees": []map[string]any{
|
||||
{"assignee": map[string]string{"id": "user-nested-1"}},
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ var ErrProviderIdentityMissing = errors.New("authoring provider identity missing
|
|||
|
||||
// isAuthoringTask returns true when the task is a Plane-origin authoring task
|
||||
// that should be routed through the IOP workspace authoring bridge. The criteria
|
||||
// are: source=="plane" and task metadata contains a "checkout" key with a
|
||||
// are: task source=="plane" and task metadata contains a "checkout" key with a
|
||||
// non-empty slot path.
|
||||
func isAuthoringTask(task storage.Task) (workitempipeline.CheckoutMetadata, bool) {
|
||||
if task.Source != "plane" {
|
||||
|
|
@ -180,7 +180,6 @@ func BuildAuthoringPushInputStrict(task storage.Task) (model.GenerateInput, bool
|
|||
Input: PushOnlyCommand,
|
||||
Metadata: map[string]string{
|
||||
"task_id": task.ID,
|
||||
"source": task.Source,
|
||||
},
|
||||
WorkspaceMetadata: &model.WorkspaceMetadata{
|
||||
Path: checkout.SlotPath,
|
||||
|
|
@ -246,7 +245,6 @@ func BuildAuthoringGenerateInputStrict(task storage.Task) (model.GenerateInput,
|
|||
Instructions: instructions,
|
||||
Metadata: map[string]string{
|
||||
"task_id": task.ID,
|
||||
"source": task.Source,
|
||||
},
|
||||
WorkspaceMetadata: &model.WorkspaceMetadata{
|
||||
Path: checkout.SlotPath,
|
||||
|
|
|
|||
|
|
@ -56,9 +56,12 @@ func TestBuildAuthoringGenerateInputPlaneOrigin(t *testing.T) {
|
|||
if input.WorkspaceMetadata.SourceBranch != "develop" {
|
||||
t.Errorf("unexpected source branch: %q", input.WorkspaceMetadata.SourceBranch)
|
||||
}
|
||||
if input.Metadata["task_id"] != "task-1" || input.Metadata["source"] != "plane" {
|
||||
if input.Metadata["task_id"] != "task-1" {
|
||||
t.Errorf("unexpected metadata: %#v", input.Metadata)
|
||||
}
|
||||
if _, ok := input.Metadata["source"]; ok {
|
||||
t.Errorf("metadata.source must not be sent to IOP-compatible responses: %#v", input.Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthoringGenerateInputPipelinePayload(t *testing.T) {
|
||||
|
|
@ -298,9 +301,12 @@ func TestBuildAuthoringPushInputPreservesWorkspaceMetadata(t *testing.T) {
|
|||
if input.WorkspaceMetadata.Tenant != "acme" {
|
||||
t.Errorf("unexpected tenant: %q", input.WorkspaceMetadata.Tenant)
|
||||
}
|
||||
if input.Metadata["task_id"] != "task-push-2" || input.Metadata["source"] != "plane" {
|
||||
if input.Metadata["task_id"] != "task-push-2" {
|
||||
t.Errorf("unexpected metadata: %#v", input.Metadata)
|
||||
}
|
||||
if _, ok := input.Metadata["source"]; ok {
|
||||
t.Errorf("metadata.source must not be sent to IOP-compatible responses: %#v", input.Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthoringPushInputNonPlaneSource(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -130,9 +130,9 @@ func (b *Bridge) Handle(ctx context.Context, ev gitoevents.BranchUpdatedEvent) e
|
|||
}
|
||||
|
||||
// enqueueDoc fetches the originating Plane work item, builds the job args, and
|
||||
// enqueues one creation sync job for a single scanned Milestone. A scanned
|
||||
// identity whose provider/work item id does not resolve to a fetchable work
|
||||
// item is a hard error so a misconfigured identity is surfaced, not swallowed.
|
||||
// enqueues one creation sync job for a single scanned Milestone. Missing work
|
||||
// items can be stale roadmap identities, so they are skipped while other fetch
|
||||
// failures still surface.
|
||||
func (b *Bridge) enqueueDoc(ctx context.Context, scan roadmapsync.ScanResult, doc ScannedMilestoneDoc) error {
|
||||
if !doc.HasIdentity {
|
||||
providerID := workitem.ProviderID("plane")
|
||||
|
|
@ -184,6 +184,14 @@ func (b *Bridge) enqueueDoc(ctx context.Context, scan roadmapsync.ScanResult, do
|
|||
|
||||
item, err := b.reader.FetchWorkItem(ctx, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, workitem.ErrNotFound) {
|
||||
b.logInfo("gito scanned milestone work item missing, skipped",
|
||||
"work_item_id", identity.WorkItemID,
|
||||
"milestone_path", identity.RoadmapMilestonePath,
|
||||
"revision", scan.Revision.Revision,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,13 +21,25 @@ func (f fakeScanner) Scan(context.Context, gitoevents.BranchUpdatedEvent) (ScanO
|
|||
}
|
||||
|
||||
type fakeReader struct {
|
||||
item workitem.WorkItem
|
||||
err error
|
||||
refs []workitem.Ref
|
||||
item workitem.WorkItem
|
||||
err error
|
||||
refs []workitem.Ref
|
||||
items map[string]workitem.WorkItem
|
||||
errs map[string]error
|
||||
}
|
||||
|
||||
func (f *fakeReader) FetchWorkItem(_ context.Context, ref workitem.Ref) (workitem.WorkItem, error) {
|
||||
f.refs = append(f.refs, ref)
|
||||
if f.errs != nil {
|
||||
if err, ok := f.errs[ref.ID]; ok {
|
||||
return workitem.WorkItem{}, err
|
||||
}
|
||||
}
|
||||
if f.items != nil {
|
||||
if item, ok := f.items[ref.ID]; ok {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
if f.err != nil {
|
||||
return workitem.WorkItem{}, f.err
|
||||
}
|
||||
|
|
@ -265,6 +277,73 @@ func TestBridgeFetchErrorIsSurfaced(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBridgeSkipsMissingWorkItemAndContinues(t *testing.T) {
|
||||
stale := matchedIdentity()
|
||||
stale.WorkItemID = "missing-wi"
|
||||
stale.RoadmapMilestonePath = "agent-roadmap/phase/p1/milestones/stale.md"
|
||||
|
||||
current := matchedIdentity()
|
||||
current.WorkItemID = "current-wi"
|
||||
current.RoadmapMilestonePath = "agent-roadmap/phase/p1/milestones/current.md"
|
||||
|
||||
out := ScanOutput{
|
||||
Result: roadmapsync.ScanResult{
|
||||
Revision: roadmapsync.Revision{Branch: "develop", Revision: "bbbb"},
|
||||
ChangedFiles: []string{
|
||||
stale.RoadmapMilestonePath,
|
||||
current.RoadmapMilestonePath,
|
||||
},
|
||||
ScannedMilestones: []roadmapsync.ScannedMilestone{
|
||||
{Path: stale.RoadmapMilestonePath, Identity: stale},
|
||||
{Path: current.RoadmapMilestonePath, Identity: current},
|
||||
},
|
||||
},
|
||||
Docs: []ScannedMilestoneDoc{
|
||||
{
|
||||
Path: stale.RoadmapMilestonePath,
|
||||
MilestoneID: stale.MilestoneID(),
|
||||
Markdown: milestoneMarkdownWithIdentity,
|
||||
HasIdentity: true,
|
||||
Identity: stale,
|
||||
},
|
||||
{
|
||||
Path: current.RoadmapMilestonePath,
|
||||
MilestoneID: current.MilestoneID(),
|
||||
Markdown: milestoneMarkdownWithIdentity,
|
||||
HasIdentity: true,
|
||||
Identity: current,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
reader := &fakeReader{
|
||||
errs: map[string]error{
|
||||
"missing-wi": workitem.ErrNotFound,
|
||||
},
|
||||
items: map[string]workitem.WorkItem{
|
||||
"current-wi": {
|
||||
Title: "Current",
|
||||
DescriptionHTML: "<p>current body</p>",
|
||||
},
|
||||
},
|
||||
}
|
||||
enq := &fakeEnqueuer{}
|
||||
bridge := newTestBridge(t, fakeScanner{out: out}, reader, enq)
|
||||
|
||||
if err := bridge.Handle(context.Background(), newScanEvent()); err != nil {
|
||||
t.Fatalf("Handle: %v", err)
|
||||
}
|
||||
if len(enq.jobs) != 1 {
|
||||
t.Fatalf("jobs: got %d, want 1", len(enq.jobs))
|
||||
}
|
||||
if enq.jobs[0].Expected.WorkItemID != "current-wi" {
|
||||
t.Fatalf("enqueued work item: got %q, want current-wi", enq.jobs[0].Expected.WorkItemID)
|
||||
}
|
||||
if len(reader.refs) != 2 {
|
||||
t.Fatalf("FetchWorkItem calls: got %d, want 2", len(reader.refs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBridgeRequiresTodoStateID(t *testing.T) {
|
||||
_, err := NewBridge(fakeScanner{}, &fakeReader{}, &fakeEnqueuer{}, nil, BridgeConfig{
|
||||
RepoID: "nomadcode",
|
||||
|
|
|
|||
|
|
@ -388,7 +388,6 @@ func buildGenerateInput(task storage.Task) model.GenerateInput {
|
|||
Instructions: instructions,
|
||||
Metadata: map[string]string{
|
||||
"task_id": task.ID,
|
||||
"source": task.Source,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,9 +119,12 @@ func TestBuildGenerateInputIncludesTaskMetadata(t *testing.T) {
|
|||
if input.Instructions != "be concise" {
|
||||
t.Fatalf("unexpected instructions: %q", input.Instructions)
|
||||
}
|
||||
if input.Metadata["task_id"] != "task-1" || input.Metadata["source"] != "plane" {
|
||||
if input.Metadata["task_id"] != "task-1" {
|
||||
t.Fatalf("unexpected metadata: %#v", input.Metadata)
|
||||
}
|
||||
if _, ok := input.Metadata["source"]; ok {
|
||||
t.Fatalf("metadata.source must not be sent to IOP-compatible responses: %#v", input.Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAgentClient struct {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import (
|
|||
"github.com/nomadcode/nomadcode-core/internal/workflow"
|
||||
)
|
||||
|
||||
var ErrInvalidRef = errors.New("invalid workitem ref")
|
||||
var (
|
||||
ErrInvalidRef = errors.New("invalid workitem ref")
|
||||
ErrNotFound = errors.New("workitem not found")
|
||||
)
|
||||
|
||||
type ProviderID string
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue