gito/agent-contract/provided/gito-forgejo-branch-events-v1.md

14 KiB

Contract: gito.forgejo-branch-events.v1

범위

이 계약은 Gito가 Forgejo push webhook을 받아 watched branch revision 변화로 정규화하고, NomadCode 같은 외부/인접 소비자에게 HTTP webhook delivery로 전달하는 경계를 정의한다. Gito는 여러 Git 플랫폼의 공통 control plane을 지향하므로 외부 소비자에게 Gito 전용 binary transport 구현을 요구하지 않는다.

Transport

  • Forgejo -> Gito: REST webhook callback.
  • Gito -> registered consumers: signed HTTP webhook delivery.
  • REST는 provider callback, external webhook delivery, bootstrap/smoke 표면에 사용한다.
  • proto-socket은 Gito 내부 control/runtime transport이며 이 외부 계약의 일부가 아니다.
  • gRPC는 이 계약 범위가 아니다.

REST Callback

POST /callbacks/forgejo/push

Forgejo push webhook을 수신한다.

Headers:

Field Required Meaning
X-Forgejo-Event 권장 push일 때만 branch update 후보로 처리한다.
X-Forgejo-Delivery 선택 provider delivery id. event payload의 delivery_id로 전달한다.
X-Forgejo-Signature FORGEJO_WEBHOOK_SECRET 설정 시 필수 request body HMAC-SHA256 hex digest. sha256= prefix도 허용한다.

Query:

Field Required Meaning
repo_id 선택 Gito local repo id override. 없으면 repository.full_name, 그 다음 repository.name을 사용한다.

Request body minimum shape:

{
  "ref": "refs/heads/develop",
  "before": "old-sha",
  "after": "new-sha",
  "repository": {
    "name": "nomadcode",
    "full_name": "owner/nomadcode",
    "default_branch": "develop"
  },
  "commits": [
    {
      "id": "new-sha",
      "added": ["agent-roadmap/phase/example/milestones/example.md"],
      "modified": ["README.md"],
      "removed": []
    }
  ]
}

Response shape:

{
  "accepted": true,
  "matched": true,
  "event": {
    "id": "event-id",
    "type": "branch.updated",
    "provider": "forgejo",
    "delivery_id": "delivery-id",
    "revision": {
      "repo_id": "nomadcode",
      "branch": "develop",
      "before": "old-sha",
      "after": "new-sha",
      "changed_files": [
        {
          "path": "agent-roadmap/phase/example/milestones/example.md",
          "change_type": "added"
        }
      ],
      "observed_at": "2026-06-13T00:00:00Z"
    },
    "created_at": "2026-06-13T00:00:00Z"
  }
}

Duplicate delivery response (additive field, only present when true):

{
  "accepted": true,
  "matched": true,
  "duplicate": true,
  "event": { "..." : "same event shape as first delivery" }
}

Idempotency: when a durable store is configured, Gito deduplicates push deliveries by X-Forgejo-Delivery header (dedupe key delivery:<id>) or by revision:<repo_id>:<branch>:<before>:<after> when the header is absent. A duplicate delivery returns the same accepted=true, matched=true response with an additional duplicate=true field and does not create a new event or outbound webhook delivery.

Status:

Status Meaning
202 Payload was accepted. matched=false means no watched branch event was emitted.
400 Malformed webhook payload.
401 Signature verification failed.
405 Method is not POST.
500 Event publish failed.

Branch Watch Bootstrap

POST /api/listeners/branches

Registers a watched branch for the in-memory MVP runtime.

Request:

{
  "repo_id": "nomadcode",
  "branch": "develop",
  "provider": "forgejo"
}

Response:

{
  "listener": {
    "id": "watch-forgejo-nomadcode-develop",
    "repo_id": "nomadcode",
    "branch": "develop",
    "provider": "forgejo",
    "created_at": "2026-06-13T00:00:00Z"
  }
}

GET /api/listeners/branches

Lists active in-memory branch watches.

Webhook Subscription Bootstrap

This is the target external subscription surface for the outbound webhook delivery Milestone. The current MVP may still expose only listener and event inspection helpers until that Milestone is implemented.

POST /api/webhook-subscriptions

Registers an HTTP endpoint that receives normalized Gito events.

Request:

{
  "name": "nomadcode-develop-wakeup",
  "target_url": "https://consumer.example.invalid/gito/events",
  "events": ["branch.updated"],
  "repo_id": "nomadcode",
  "branch": "develop",
  "secret_ref": "secret://gito/webhooks/nomadcode"
}

Response:

{
  "subscription": {
    "id": "webhook-sub-nomadcode-develop",
    "name": "nomadcode-develop-wakeup",
    "events": ["branch.updated"],
    "repo_id": "nomadcode",
    "branch": "develop",
    "created_at": "2026-06-13T00:00:00Z"
  }
}

target_url and secret_ref are examples only. Raw signing secrets, tokens, or private endpoint values must not be stored in tracked docs.

Event Inspection

GET /api/events

Lists recent in-memory events. This is a smoke/debug surface, not the primary internal consumption path.

Response:

{
  "events": [
    {
      "id": "event-id",
      "type": "branch.updated",
      "provider": "forgejo",
      "delivery_id": "delivery-id",
      "revision": {
        "repo_id": "nomadcode",
        "branch": "develop",
        "before": "old-sha",
        "after": "new-sha",
        "changed_files": [
          {
            "path": "agent-roadmap/phase/example/milestones/example.md",
            "change_type": "modified"
          }
        ],
        "observed_at": "2026-06-13T00:00:00Z"
      },
      "created_at": "2026-06-13T00:00:00Z"
    }
  ]
}

HTTP Webhook Delivery

Gito delivers matched branch.updated events to registered HTTP endpoints with a GitHub-style webhook model. The delivery body is JSON.

Headers:

Field Required Meaning
X-Gito-Event 필수 Normalized event type, for example branch.updated.
X-Gito-Delivery 필수 Stable delivery id. Consumers use this for idempotency.
X-Gito-Signature secret_ref 설정 시 필수 HMAC-SHA256 digest over the request body. sha256= prefix is allowed.

Request body:

{
  "id": "event-id",
  "type": "branch.updated",
  "provider": "forgejo",
  "delivery_id": "forgejo-delivery-id",
  "repo_id": "nomadcode",
  "branch": "develop",
  "before": "old-sha",
  "after": "new-sha",
  "changed_files": [
    {
      "path": "README.md",
      "change_type": "modified"
    }
  ],
  "observed_at": "2026-06-13T00:00:00Z",
  "created_at": "2026-06-13T00:00:00Z"
}

Delivery response handling:

Status Meaning
2xx Delivery accepted.
400 Consumer rejected malformed payload; retry is not useful unless config changed.
401 or 403 Signature/auth failure; disable or pause delivery until config is fixed.
404 or 410 Endpoint no longer exists; delivery should be disabled or marked failed.
429 or 5xx Retry with backoff.

Delivery idempotency is consumer-visible through X-Gito-Delivery. Gito records attempt state in the durable outbox/delivery store and must not treat webhook delivery success as proof that the consumer has applied source-of-truth changes.

Base Schema 및 Extension 경계

branch.updated base payload 필드:

필드 설명
id Gito event id
type 고정값 branch.updated
provider forgejo 등 provider 이름
delivery_id provider delivery id (있을 때만)
repo_id Gito local repo id
branch branch 이름
before 이전 revision SHA
after 새 revision SHA
changed_files [{path, change_type}] 목록
observed_at Gito가 payload를 정규화한 시각 (RFC3339)
created_at Gito가 event를 기록/발행한 시각 (RFC3339)

위 필드는 모두 consumer-neutral한 범용 Git event 정보다. NomadCode 전용 필수 필드는 base payload에 포함하지 않는다. consumer-specific 확장이 필요하면 optional payload.metadata 또는 별도 optional field로만 허용한다.

Field Semantics

Field Meaning
repo_id Gito-local repo id used by watches and downstream sync.
branch Branch name without refs/heads/.
before Previous revision SHA from Forgejo push payload.
after New revision SHA from Forgejo push payload.
changed_files[].path Path changed by the push payload or future revision scan.
changed_files[].change_type added, modified, deleted, renamed, or copied.
delivery_id Provider delivery id when supplied.
observed_at Time Gito normalized the provider payload.
created_at Time Gito recorded/emitted the event.

Consumer Responsibilities

  • NomadCode treats branch.updated as a wakeup signal.
  • NomadCode must verify the target branch revision before mutating Plane, agent-roadmap, or other source-of-truth state.
  • NomadCode should use repo_id, branch, before, after, and changed_files to decide whether roadmap sync is relevant.
  • NomadCode should not treat webhook file lists as the only proof of state; for critical updates it should fetch/scan the target branch.

Consumer Interop Procedure

NomadCode 또는 다른 consumer가 Gito branch event webhook delivery를 연동할 때 따르는 최소 절차다.

  1. POST /api/listeners/branches로 watched branch를 등록한다. branch watch가 없으면 이벤트가 발행되지 않는다.
  2. Consumer는 HTTP endpoint와 event/repo/branch filter를 Gito webhook subscription으로 등록한다.
  3. Gito는 Forgejo push webhook 수신 후 matched branch에 대해 durable branch.updated event를 기록한다.
  4. Gito는 matching subscription의 target_url로 signed HTTP POST delivery를 보낸다.
  5. Consumer는 base payload(repo_id, branch, before, after, changed_files)를 자기 내부 wakeup 로직에 매핑한다.
  6. 중요 동작 전에 before/after SHA와 changed_files를 참고하되, provider file list나 webhook delivery를 유일한 상태 증거로 쓰지 않는다.

NomadCode interop 확인 기준: NomadCode dev consumer가 generic branch.updated HTTP webhook delivery를 수신해 내부 wakeup으로 매핑한다. base payload에 NomadCode 전용 필수 필드가 없다. 다른 consumer도 같은 schema를 재사용할 수 있다.

Provider Responsibilities

  • Gito must not store raw provider secrets in tracked docs or event payloads.
  • Gito must reject invalid signatures when FORGEJO_WEBHOOK_SECRET is configured.
  • Gito should accept unsupported or unwatched provider events without side effects when safe, returning matched=false.
  • Gito should eventually persist branch watches, revision cursors, and event outbox records in PostgreSQL; the MVP keeps them in memory.

운영 확인 절차

listener bootstrap, webhook subscription, /api/listeners/branches, /api/events를 secret 없이 재현 가능하게 확인하는 절차다.

필요한 환경 변수

변수 필수 기본값 설명
APP_ENV 아니오 development 앱 환경
FORGEJO_WEBHOOK_SECRET 아니오 (없으면 서명 검증 생략) Forgejo webhook 서명 키
DATABASE_URL 아니오 (없으면 in-memory) PostgreSQL DSN

기본 smoke는 FORGEJO_WEBHOOK_SECRETDATABASE_URL 없이 실행할 수 있다.

Step 1 — Branch watch 등록

curl -s -X POST http://localhost:8080/api/listeners/branches \
  -H "Content-Type: application/json" \
  -d '{"repo_id":"nomadcode","branch":"develop","provider":"forgejo"}'

기대 응답: 201 Created, {"listener": {...}}

Step 2 — 등록된 watch 목록 확인

curl -s http://localhost:8080/api/listeners/branches

Step 3 — webhook subscription 등록

curl -s -X POST http://localhost:8080/api/webhook-subscriptions \
  -H "Content-Type: application/json" \
  -d '{"name":"nomadcode-develop-wakeup","target_url":"https://consumer.example.invalid/gito/events","events":["branch.updated"],"repo_id":"nomadcode","branch":"develop","secret_ref":"secret://gito/webhooks/nomadcode"}'

기대 응답: 201 Created, {"subscription": {...}} 이 endpoint는 outbound webhook delivery 구현 Milestone에서 추가한다.

Step 4 — webhook delivery worker 통합 테스트로 연결 확인

cd services/core && go test -run TestWebhookDeliveryBranchUpdated ./internal/controlplane/ -v -count=1

이 테스트는 outbound webhook delivery 구현 Milestone에서 추가한다.

Step 5 — Forgejo push 트리거 (서명 없는 경우)

curl -s -X POST "http://localhost:8080/callbacks/forgejo/push?repo_id=nomadcode" \
  -H "Content-Type: application/json" \
  -H "X-Forgejo-Event: push" \
  -H "X-Forgejo-Delivery: smoke-delivery-1" \
  -d '{"ref":"refs/heads/develop","before":"old-sha","after":"new-sha","repository":{"name":"nomadcode","full_name":"owner/nomadcode"},"commits":[{"id":"new-sha","modified":["README.md"],"added":[],"removed":[]}]}'

기대 응답: {"accepted":true,"matched":true,"event":{...}}

Step 6 — 발행된 event 확인

curl -s http://localhost:8080/api/events

기대 응답에 "type":"branch.updated", "before":"old-sha", "after":"new-sha", changed_files가 포함된다.

금지 사항

  • Raw webhook secret, token, password, or credential value must not appear in tracked docs, event payloads, logs, or contract examples.
  • Provider webhook callbacks must not directly mutate NomadCode or Plane state.
  • Gito -> external consumer branch event delivery must not require proto-socket.
  • Consumer webhook delivery must not be treated as final source-of-truth state.