Files
gitea-agent/harness/operations/repository-cache.md
T

23 KiB

Repository cache 운영 지침

이 문서는 Gitea Agent harness가 사용하는 local repository cache의 canonical 운영 규칙이다. 모든 agent와 skill은 cache를 생성·갱신·검증할 때 이 문서와 Gitea authenticated access 운영 지침을 읽고 따라야 하며 별도의 완화된 규칙을 만들지 않는다.

목적과 범위

Repository cache는 Gitea repository의 Git 객체와 refs를 재사용하기 위한 bare mirror다. 문서 근거 조사와 Issue별 worktree 생성의 source로 사용할 수 있지만 그 자체는 checkout, 작업 공간 또는 외부 상태의 정본이 아니다.

  • Canonical access 계약을 충족하는 authenticated Gitea API readback이 repository identity와 authoritative ref의 정본이다.
  • repository.git은 확인한 Gitea 상태와 일치할 때만 사용한다.
  • cache 생성과 갱신은 local filesystem 및 Git refs만 변경한다. Gitea에는 write하지 않는다.

Harness root 결정

Repository cache 경로는 고정된 machine-local 절대경로나 현재 작업 directory에서 추측하지 않는다. Agent가 이번 작업에서 read_file로 실제 읽은 이 문서의 경로가 exact <harness-root>/harness/operations/repository-cache.md 형식인지 확인하고, 그 경로에서 <harness-root>를 계산한다. 따라서 repository 전체를 다른 위치에 설치해도 cache root는 항상 <harness-root>/repositories다.

  • 이 문서의 경로, operations, harness와 <harness-root>를 각각 lstat하고 symlink가 아닌지 확인한다.
  • 문서 경로가 위 구조와 exact-match하지 않거나 <harness-root>를 하나로 결정할 수 없으면 fail-closed한다.
  • pwd, shell 시작 directory, 사용자 home, 다른 checkout, 환경변수 또는 과거 session 값을 root 결정 근거로 사용하지 않는다.
  • Source checkout과 runtime cache가 같은 root 아래 있더라도 repository source로 혼동하지 않는다. Runtime cache는 <harness-root>/repositories 아래에서만 관리한다.

경로 구조

<harness-root>/repositories/
├── .locks/
│   └── <owner>/
│       └── <repository>.bootstrap.lock
└── <owner>/
    └── <repository>/
        ├── repository.git
        ├── locks/
        │   ├── repository.lock
        │   └── issue-<issue-number>.lock
        └── worktrees/
            └── issue-<issue-number>/
                ├── state.json
                └── checkout/
  • .locks/<owner>/<repository>.bootstrap.lock은 cache가 아직 없을 때도 사용할 수 있는 초기화 mutex다.
  • repository.lock은 clone, fetch와 ref 검증을 직렬화한다.
  • issue-<issue-number>.lock은 같은 Issue를 대상으로 하는 worker와 외부 write workflow를 직렬화한다. 필요할 때 생성한다.
  • repository.git은 git clone --mirror로 만든 bare mirror다.
  • worktrees/issue-<issue-number>/checkout은 해당 Issue의 유일한 linked worktree다. state.json은 checkout 밖에서 worktree identity와 생성 근거를 보존한다.
  • checkout과 worktree metadata는 위 canonical 위치에만 생성한다. 다른 checkout, 임시 clone 또는 repository 밖의 workspace를 구현 근거로 사용하지 않는다.

식별자와 경로 안전성

  1. owner와 repository는 요청에서 받은 값을 보존하되 각각 안전한 단일 경로 요소인지 검증한다.
  2. 빈 값, 경로 구분자, NUL, .과 .. 또는 traversal을 허용하지 않는다.
  3. 위 규칙으로 계산하고 검증한 <harness-root>만 trusted harness root로 사용한다. Exact path가 symlink가 아닌 directory이고 world-writable이 아닌지 확인한다. NFS mount root의 numeric owner가 실행 UID와 달라도 허용한다.
  4. 기존 repositories는 lstat으로 검사해 symlink가 아닌 directory, 실행 role 소유이며 world-writable이 아님을 확인한다. Group-writable이면 directory의 numeric GID가 현재 system에서 해석되고, 해당 GID를 primary group으로 쓰는 계정과 group member 목록을 합친 effective member가 실행 role 하나뿐임을 확인해야 한다. Group identity나 전체 membership을 확인할 수 없거나 다른 계정이 포함되면 fail-closed한다.
  5. 새 repositories를 만들 때는 실행 role 소유와 mode 0700으로 생성한다. 기존 directory가 4번의 검증을 통과하면 mode 0700, 0750, 0755, 0770 또는 0775를 자동으로 chmod하지 않고 그대로 사용할 수 있다.
  6. Trust boundary 아래 .locks, owner, repository와 locks는 생성 전후 lstat으로 검사한다. 모두 symlink가 아닌 directory, 실행 role 소유, mode 0700이어야 한다.
  7. Lock 파일과 임시 credential helper는 실행 role 소유와 mode 0600을 요구한다.
  8. worktrees, issue-<issue-number>와 checkout은 symlink가 아닌 directory이고 실행 role 소유여야 한다. worktrees와 Issue directory는 mode 0700, state.json은 mode 0600이어야 하며 checkout과 그 상위 runtime 경로는 group 또는 world-writable이면 안 된다.
  9. 기존 path가 malformed 상태면 삭제, chmod, chown, reset 또는 덮어쓰기로 자동 복구하지 않고 fail-closed한다. 권한 변경은 운영자가 exact path와 mode를 명시적으로 승인한 경우에만 수행하고 즉시 재검증한다.

Lock 규칙

  • Linux OS advisory lock을 사용한다. lock file descriptor는 symlink를 따라가지 않는 방식으로 열고 원자적으로 lock을 획득한다.
  • 독립적인 신규 cache 초기화 순서는 bootstrap.lock → repository.lock이다.
  • Issue workflow가 기존 cache를 사용할 때의 순서는 issue-<issue-number>.lock → repository.lock이다.
  • Issue 작업에 사용할 cache가 없으면 Issue lock 획득 전에 신규 cache 초기화 절차를 완료한다. 초기화 중에는 Issue를 변경하지 않는다. Bootstrap의 repository lock과 bootstrap lock을 모두 해제한 뒤 Issue lock을 획득하며, 이후에는 기존 cache 사용 순서인 issue-<issue-number>.lock → repository.lock을 따른다. Issue 작업의 기준 snapshot은 Issue lock 획득 후 읽는다.
  • Worktree 생성, 등록, lock, 이동, 제거 또는 repair는 Issue lock을 유지한 채 repository lock을 추가로 획득해서 수행한다. 일반 파일 편집, 테스트와 commit에는 Issue lock만 유지하고 repository lock은 보유하지 않는다.
  • 같은 repository의 다른 Issue worktree를 검사, 변경 또는 제거하지 않는다. 한 Issue workflow는 자신의 exact Issue worktree만 다룬다.
  • 반대 순서로 lock을 획득하거나 이미 잡은 상위 lock을 해제한 뒤 하위 작업을 계속하지 않는다.
  • 정상 종료, 오류와 중단 경로 모두에서 descriptor를 닫아 lock을 해제한다.
  • lock 파일은 unlink하지 않는다. 프로세스 종료 시 kernel이 advisory lock을 해제하므로 남아 있는 파일을 stale active lock으로 간주하거나 PID 기준으로 삭제하지 않는다.

신규 cache 초기화

  1. Authenticated Gitea API adapter로 허용된 role의 stable account identity를 확인한다. MCP면 get_me, 직접 REST면 GET /api/v1/user를 사용한다.
  2. exact owner/repository metadata, clone URL, default branch와 branch SHA를 Gitea에서 읽는다.
  3. bootstrap lock을 획득한 뒤 target을 다시 검사한다. 이미 유효한 cache가 있으면 새로 만들지 않고 기존 cache 검증 절차로 전환한다.
  4. trusted parent 아래 같은 filesystem에 run-unique sibling staging directory를 mode 0700으로 만든다.
  5. staging에 locks/repository.lock을 mode 0600으로 만들고 repository lock을 획득한다. bootstrap lock과 repository lock을 모두 유지한 상태에서 clone, ref 검증과 publication을 수행한다.
  6. credential이 필요하면 role profile의 credential을 short-lived child process로만 전달한다. credential을 URL, argv, Git config, log 또는 remote URL에 저장하지 않는다.
  7. staging 안에 git clone --mirror로 repository.git을 만든다.
  8. staging mirror가 bare repository인지, origin이 exact owner/repository인지, default branch ref가 Gitea SHA와 일치하는지 확인한다.
  9. staging 내용과 parent directory를 flush한 뒤 no-replace atomic rename으로 target directory를 게시한다. NFS가 no-replace primitive를 지원하지 않는 경우에만, bootstrap lock을 유지하고 parent가 실행 role 소유의 mode 0700이며 target이 즉시 lstat에서 부재함을 확인한 뒤 동일 filesystem의 atomic rename을 사용할 수 있다. target이 존재하거나 조건을 증명할 수 없으면 fallback하지 않는다.
  10. 게시 직후 target의 path 유형, 소유권, 권한, bare 상태, remote identity, default branch와 SHA를 다시 검증한다.
  11. 모든 검증이 끝난 후 repository lock과 bootstrap lock을 역순으로 해제한다.

초기화 실패 시 현재 run이 만든 staging만 안전하게 제거할 수 있다. final target, 다른 run의 staging 또는 정체를 확인할 수 없는 잔여 경로는 자동 삭제하지 않는다.

기존 cache 갱신

  1. 필요한 상위 lock을 유지한 채 repository lock을 획득한다.
  2. target과 repository.git의 path, ownership, permissions와 bare 상태를 다시 검증한다.
  3. origin의 canonical owner/repository identity가 요청과 일치하는지 확인한다. URL 문자열의 credential이나 transport hostname만으로 identity를 판정하지 않는다.
  4. 명시적으로 검증한 origin에서 mirror refs를 prune/fetch한다. 임의 remote 교체, force reset 또는 다른 repository 재사용은 금지한다.
  5. Gitea에서 default branch와 SHA를 다시 읽고 local ref와 일치하는지 확인한다.
  6. 일치한 exact SHA를 이번 작업의 pinned source revision으로 기록한다.
  7. repository lock을 해제한다.

Issue worktree 모델

  • 한 repository의 한 Issue에는 persistent linked worktree 하나만 허용한다. worktree는 구현 결과가 안전하게 전달되거나 명시적으로 폐기될 때까지 세션 간 보존한다.
  • Worktree는 repository.git의 exact commit에서 detached HEAD로 생성한다. moving branch, tag, abbreviated SHA 또는 HEAD를 생성 기준으로 사용하지 않는다.
  • Mirror의 refspec은 refs/*를 직접 mirror하므로 구현용 local branch를 repository.git의 refs/heads/*에 만들지 않는다. namespace를 붙인 local branch도 허용하지 않는다.
  • Detached worktree에서 commit할 수 있다. 해당 commit은 worktree HEAD가 가리키는 동안 reachable 상태로 보존한다.
  • 구현 요청은 local 편집, 테스트와 detached commit을 승인할 수 있지만 remote branch 생성, push, PR, Issue 변경 또는 merge를 자동 승인하지 않는다. 외부 write는 별도의 명시적 승인과 readback 계약을 따른다.
  • git worktree prune, 전체 worktree repair 또는 다른 Issue에 영향을 줄 수 있는 일괄 cleanup을 자동 실행하지 않는다.

Worktree state 계약

state.json은 checkout 안에 넣거나 repository source로 commit하지 않는다. credential, token, clone credential URL, 환경변수 값, PID 또는 임시 credential helper 경로를 기록하지 않는다. 최소 형식은 다음과 같다.

{
  "schema": "gitea-agent.worktree/v1",
  "owner": "<owner>",
  "repository": "<repository>",
  "issue": 1,
  "base_sha": "<full-commit-sha>",
  "issue_updated_at": "<authoritative-timestamp>",
  "issue_body_sha256": "<sha256>",
  "created_by_role": "<role>",
  "mode": "detached"
}
  • base_sha는 40자리 full commit SHA이고 Gitea 또는 승인된 Discovery에서 확인한 구현 기준 revision이어야 한다.
  • issue_body_sha256은 구현 승인의 근거가 된 authoritative Issue body의 exact UTF-8 bytes에 대해 계산한다.
  • created_by_role은 credential 값이 아닌 harness role identity다. PID나 현재 process 존재 여부를 worktree 소유권 또는 활성 상태의 정본으로 사용하지 않는다.
  • 파일은 run-unique sibling temporary file에 mode 0600으로 완성하고 flush한 뒤 no-replace atomic rename으로 게시한다. 기존 state.json을 덮어쓰지 않는다.

Worktree admission gate

Worktree를 신규 생성하거나 기존 worktree에서 구현을 재개하기 전에 다음을 모두 확인한다.

  1. exact owner, repository와 Issue 번호가 검증됐고 Issue lock을 획득했다.
  2. Authenticated Gitea API adapter로 role identity, exact repository metadata, Issue body, updated_at과 구현 승인 상태를 읽었다.
  3. repository cache가 이 문서의 path, ownership, permission, bare, remote identity와 authoritative ref 검증을 통과했다.
  4. 구현에 사용할 exact base SHA와 그 출처를 결정했다. Discovery pinned revision과 현재 default branch SHA가 다르면 어느 한쪽을 자동 선택하지 않고 변경을 조사해 요구사항과 충돌하지 않는 base를 확정하거나 중단한다.
  5. repository.git에 exact base SHA의 commit object가 존재한다. 문자열 형식이나 object 존재만으로 Gitea 또는 승인된 Discovery에서 유래했다는 검증을 대체하지 않는다.
  6. canonical target path, state.json과 Git worktree registration의 존재 여부를 함께 조사했다.
  7. 신규 생성이면 target path와 동일 Issue registration이 모두 없어야 한다. 재사용이면 아래의 기존 worktree 검증을 모두 통과해야 한다.

Gate가 끝난 뒤 write 직전에 Issue body, updated_at과 구현 승인 상태를 다시 읽는다. admission snapshot과 다르면 worktree를 생성하거나 기존 worktree를 수정하지 않는다.

Worktree 신규 생성

  1. Issue lock을 유지한 채 repository lock을 획득한다.
  2. cache와 base commit을 다시 검증하고 worktrees와 Issue directory를 canonical mode로 생성한 뒤 즉시 lstat한다.
  3. final checkout, state.json과 동일 Issue의 Git worktree registration이 모두 없는지 다시 확인한다.
  4. git --git-dir=<repository.git> worktree add --detach <checkout> <full-base-sha>와 동등한 호출로 exact final checkout을 생성한다. branch 생성 option을 사용하지 않는다.
  5. worktree를 Issue 용도로 git worktree lock하고 reason에 repository credential이나 민감정보 없이 Issue 번호를 기록한다.
  6. state.json을 state 계약에 따라 원자적으로 게시한다.
  7. 생성 직후 다음을 모두 검증한다.
    • git rev-parse HEAD가 exact base SHA와 일치한다.
    • git symbolic-ref -q HEAD가 성공하지 않아 detached HEAD임이 확인된다.
    • git status --porcelain이 비어 있다.
    • git rev-parse --git-common-dir가 canonical repository.git과 동일하다.
    • git worktree list --porcelain에 exact checkout path가 한 번만 있고 locked 상태다.
    • checkout path와 state.json의 identity, ownership과 permission이 canonical 계약을 충족한다.
  8. 모든 검증 후 repository lock을 해제한다. Issue lock은 해당 Issue workflow가 끝날 때까지 유지한다.

Git 명령의 성공 exit만으로 생성 완료를 판정하지 않는다. Git registration, checkout, state와 Issue snapshot을 모두 검증해야 한다.

기존 worktree 재사용

기존 directory 또는 Git registration 중 하나라도 발견되면 신규 생성으로 덮어쓰지 않는다. Issue lock을 유지하고 repository lock 아래에서 다음을 모두 확인한다.

  • state.json이 유효한 gitea-agent.worktree/v1이고 owner, repository, Issue와 role이 현재 workflow와 일치한다.
  • exact checkout이 canonical repository.git에 한 번만 등록돼 있고 locked 상태다.
  • checkout의 common Git directory가 canonical repository.git이다.
  • HEAD, index, tracked 변경과 untracked 파일을 모두 읽을 수 있다.
  • HEAD가 base_sha와 같거나 base_sha가 HEAD의 ancestor다. ancestor가 아니면 history를 자동 재작성하지 않는다.
  • merge, rebase, cherry-pick, revert, bisect 또는 다른 미완료 Git operation이 없다.
  • 현재 Issue body와 updated_at을 state의 snapshot과 비교해 drift 여부를 판정했다.

Dirty worktree, untracked 파일 또는 base 이후 commit은 진행 중 구현에서 정상일 수 있으므로 존재만으로 삭제하거나 초기화하지 않는다. git reset --hard, git clean, 자동 checkout, switch, rebase 또는 history rewrite를 수행하지 않는다. Issue가 변경됐다면 이전과 현재 snapshot, 기존 diff·commit과 acceptance criteria를 비교해 재사용 가능성이 명확할 때만 state를 별도 승인된 절차로 갱신하고, 판단할 수 없으면 fail-closed한다.

Worktree 작업과 delivery

  • 일반 편집, 테스트와 commit은 exact Issue lock 아래에서만 수행한다. 현재 worktree 외의 source, 다른 checkout 또는 installed package를 수정하거나 구현 근거로 사용하지 않는다.
  • commit 전후 base SHA, HEAD SHA, staged·unstaged·untracked 상태를 기록한다. detached 상태를 local branch로 자동 전환하지 않는다.
  • 명시적으로 승인된 push는 source와 destination을 모두 지정해 HEAD:refs/heads/<approved-branch> 형태와 동등하게 수행한다. destination 없는 push, force push와 mirror push는 금지한다.
  • Push 후 Gitea에서 exact remote branch SHA를 다시 읽어 local HEAD와 일치하는 경우에만 전달 완료로 판정한다. PR 생성, merge, Issue comment·label·state 변경은 각각 별도 승인이 필요하다.
  • 테스트 성공, local commit 또는 agent session 종료만으로 worktree를 제거하지 않는다.

Worktree 실패 처리와 제거

현재 run이 worktree를 생성했고 편집이나 commit이 시작되지 않았으며 worktree가 clean하고 HEAD가 base SHA와 같다는 것을 증명할 때만 생성 실패를 자동 cleanup할 수 있다. 이때 Issue lock과 repository lock을 모두 유지하고 worktree lock을 해제한 뒤 git worktree remove와 동등한 Git operation으로 exact registration과 checkout을 제거한다. filesystem 삭제를 먼저 수행하지 않는다.

다음 상태는 자동 cleanup하지 않고 보존한 채 partial 또는 fail-closed로 보고한다.

  • dirty 또는 untracked 상태
  • base 이후 commit 존재
  • 생성 주체, identity 또는 base를 확인할 수 없음
  • state.json, checkout path와 Git registration 불일치
  • symlink, ownership 또는 permission 이상
  • 미완료 Git operation
  • unlock, remove 또는 cleanup 결과가 불확실함

정상 worktree 제거는 사용자가 exact worktree 폐기를 승인했거나, 승인된 delivery가 완료되어 remote branch SHA가 local HEAD와 일치함을 authoritative readback한 경우에만 수행한다. 제거 전 base SHA, final HEAD SHA, dirty·untracked 상태, 미전달 commit과 remote 보존 여부를 확인하고 보고한다. Dirty 상태, 미전달 commit 또는 판정 불가능한 파일이 있으면 --force, filesystem 재귀 삭제, reset 또는 clean으로 우회하지 않는다.

제거할 때는 Issue lock과 repository lock 아래에서 worktree lock을 해제하고 Git operation으로 checkout과 registration을 먼저 제거한다. 두 대상의 부재를 확인한 뒤 exact state.json과 비어 있는 Issue worktree directory만 제거하며, 현재 run이 만든 state temporary file이 있으면 identity를 확인한 뒤 함께 제거한다. 다른 파일이나 하위 directory가 남아 있으면 재귀 삭제하지 않고 중단한다. 마지막으로 exact checkout, Git registration, state.json과 Issue worktree directory가 모두 없는지 다시 검증한다.

Credential 안전

  • credential 값은 role profile의 .env에만 저장하고 mode 0600을 요구한다.
  • worker에게 credential 값 또는 credential-bearing URL을 전달하지 않는다.
  • Git 인증이 필요하면 short-lived askpass 또는 동등한 child-process mechanism을 사용하고 종료 시 제거한다.
  • inherited credential, 다른 role의 credential 또는 interactive prompt로 fallback하지 않는다.
  • 인증에 실패하면 public clone이나 unauthenticated API·content read를 initialized mirror의 증거로 대체하지 않고 중단한다.
  • Worktree source, state, Git config, reflog message, commit, patch, test artifact 또는 push refspec에 credential을 기록하지 않는다.

Fail-closed 조건

다음 중 하나라도 발생하면 cache를 사용하거나 자동 복구하지 않는다.

  • Gitea identity 또는 repository metadata를 확인할 수 없음
  • symlink, ownership, permission 또는 path type 불일치
  • non-bare 또는 손상된 repository.git
  • remote repository identity 불일치
  • Gitea default branch SHA와 local ref 불일치
  • lock 획득 실패 또는 lock order 위반
  • credential 출처나 cleanup을 확인할 수 없음
  • 초기화·fetch 결과가 불확실함

Issue worktree workflow에서는 다음 조건도 fail-closed한다. Cache만 조사하는 workflow에서 worktree가 존재하지 않는 것은 오류가 아니다.

  • Issue identity, 구현 승인 또는 Issue snapshot drift를 판정할 수 없음
  • worktree path가 canonical 위치 밖이거나 symlink, ownership 또는 permission 계약을 위반함
  • target Issue의 worktree path 또는 Git registration이 존재하지만 state.json이 누락, 손상 또는 현재 owner, repository, Issue, role과 불일치함
  • worktree common Git directory가 exact repository.git과 다름
  • 동일 Issue에 여러 worktree가 등록됐거나 path와 registration이 불일치함
  • attached branch 상태이거나 mirror에 구현용 local branch가 생성됨
  • base SHA의 authoritative 출처 또는 commit object를 확인할 수 없음
  • 미완료 Git operation 또는 worktree cleanup 결과가 불확실함

검증 및 보고

완료 보고에는 다음을 포함한다.

  • authenticated Gitea identity
  • exact owner/repository
  • cache path와 신규 생성 또는 기존 재사용 여부
  • bare repository 검증 결과
  • canonical remote identity
  • default branch와 Gitea/local SHA
  • 획득한 lock 순서
  • credential 값이 남지 않았다는 확인
  • cache workflow의 partial 또는 fail-closed 상태

Issue worktree workflow의 완료 보고에는 다음도 포함한다.

  • Issue 번호, 승인 snapshot의 updated_at과 body digest
  • worktree path, 신규 생성 또는 기존 재사용 여부와 locked·detached 검증
  • base SHA, final HEAD SHA와 base-to-HEAD ancestry
  • staged, unstaged, untracked, 미완료 Git operation과 미전달 commit 상태
  • delivery가 승인됐다면 remote branch와 Gitea/local SHA readback
  • partial 또는 fail-closed 상태

프로세스 exit와 clone/fetch 또는 worktree 명령의 성공 메시지만으로 완료를 주장하지 않는다. Cache workflow는 final cache path와 Gitea authoritative state를, Issue worktree workflow는 여기에 exact worktree registration, checkout, state와 Issue snapshot을 더해 모두 다시 읽은 경우에만 완료로 판정한다.