37 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/
└── verification/
└── <run-id>/
.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를 구현 근거로 사용하지 않는다.
식별자와 경로 안전성
owner와repository는 요청에서 받은 값을 보존하되 각각 안전한 단일 경로 요소인지 검증한다.- 빈 값, 경로 구분자, NUL,
.과..또는 traversal을 허용하지 않는다. - 위 규칙으로 계산하고 검증한
<harness-root>만 trusted harness root로 사용한다. Exact path가 symlink가 아닌 directory이고 world-writable이 아닌지 확인한다. NFS mount root의 numeric owner가 실행 UID와 달라도 허용한다. - 기존
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한다. - 새
repositories를 만들 때는 실행 role 소유와 mode0700으로 생성한다. 기존 directory가 4번의 검증을 통과하면 mode0700,0750,0755,0770또는0775를 자동으로 chmod하지 않고 그대로 사용할 수 있다. - Trust boundary 아래
.locks, owner, repository와locks는 생성 전후lstat으로 검사한다. 모두 symlink가 아닌 directory, 실행 role 소유, mode0700이어야 한다. - Lock 파일과 임시 credential helper는 실행 role 소유와 mode
0600을 요구한다. worktrees,issue-<issue-number>와checkout은 symlink가 아닌 directory이고 실행 role 소유여야 한다.worktrees와 Issue directory는 mode0700,state.json은 mode0600이어야 하며 checkout과 그 상위 runtime 경로는 group 또는 world-writable이면 안 된다.- 기존 path가 malformed 상태면 삭제, chmod, chown, reset 또는 덮어쓰기로 자동 복구하지 않고 fail-closed한다. 권한 변경은 운영자가 exact path와 mode를 명시적으로 승인한 경우에만 수행하고 즉시 재검증한다.
Managed-lock broker (Issue workflow 필수)
Bootstrap 이후 모든 Issue workflow는 role-neutral shared managed-lock broker 하나를 사용한다. 개별 impl/spec/review skill이 직접 flock을 획득하거나 broker 규칙을 재정의하지 않는다. Broker executable은 harness/lib/managed_lock_broker.py이며 authenticated request action은 status, renew, acquire_repository, release_repository, release이고 최대 TTL은 3600초다.
- Bootstrap lock을 해제한 뒤 broker를 시작한다. checkout 밖 trusted parent 아래 role-owned mode
0700runtime directory를 미리 만들고, run-unique handle과 socket path를 사용한다. Handle file은 mode0600이고 socket은 mode0600이며 둘 다 symlink가 아니어야 한다. serve는 tracked background process로 시작하고 ready JSON을 기다린 뒤에만 Issue 작업을 시작한다. Ready 전에 broker는 bound filesystem socket node의 type·owner UID·device/inode와 exact mode0600을 검증한다. Socket FD의 sockfs metadata는 이 검증을 대신하지 못한다. Client는 raw token을 직접 read/log하지 않으며, broker publicrequestAPI가 handle을 인증하고 token을 캡슐화한다.
이 broker의 보안 경계에서 filesystem owner UID는 trusted principal이다. mode 0700/0600과 random bearer token은 다른 UID 또는 foreign token을 사용한 작업을 막지만, 같은 UID를 공유하는 상호 비신뢰 프로세스를 서로 격리하지는 못한다. 그런 격리가 필요한 배포는 각 role을 distinct UID로 실행해야 한다.
-
Issue lock은 broker의
serve가 관리한다. Repository lock이 필요하면 같은 handle로acquire_repository를 request하고, 끝나면release_repository를 request한다. 중첩 repository lock에 rawflock ... sleep또는 별도 lock holder를 사용하지 않는다. -
TTL 만료 전에
renew를 request한다. Broker crash 뒤 kernel이 advisory lock을 해제해도 stale handle은 진단 정보일 뿐이며probe가 authoritative하다. -
정상, 오류와 cancel 경로는 동일한 cleanup을 수행한다: 보유 중이면
release_repository,release, broker process wait/reap, 그리고 Issueprobe를 실행해free=true를 요구한다. 다른 Issue workflow가 합법적으로 repository lock을 먼저 획득할 수 있으므로 완료 gate는 Issue resource를 검사하고, 종료한 broker process가 자신의 repository FD를 더 이상 보유하지 않음을 wait/reap으로 증명한다. 이 순서를 검증하기 전에는 completed/log/report를 주장하지 않는다. Lock 파일은 절대 unlink하지 않는다. -
소켓 정리는 부모 directory FD를 기준으로 기록한 filesystem device/inode·type·owner가 일치하는 경로만 삭제한다. 경로가 다른 객체나 symlink로 교체되면 그 객체는 삭제하지 않고
socket-identity-changed로 보고한다. 같은 UID의 경쟁 프로세스까지 원자적 격리를 보장하지 않으며, SIGKILL/crash 뒤 소켓 삭제를 보장하지 않는다. Stale 경로를 자동 삭제하거나 재사용하지 않는다. 첫 소켓 정리 실패 뒤에는 부모 FD를 닫고 경로를 다시 열어 자동 재시도하지 않는다. 잔여 경로의 제거는 운영자가 exact 경로와 소유권을 확인하는 별도 복구이며, 반복 cleanup은 이전 부분 실패를 보존한다. -
정상 release 응답은 기존
{"status":"released"}를 유지한다. 정리가 일부 실패하면 broker는{"error":"cleanup-incomplete","cleanup":...}를 응답하고 종료하며,cleanup의lease_released,socket_removed,handle_saved,errors로 해제와 잔여 정리를 구분한다. Handle 기록 가능 시 같은 결과를 비밀값 없이 저장하고 stderr에도 오류 코드만 보고한다. 부분 실패는 nonzero process exit로 보고하며, 성공한 cleanup을 재적용하거나 실패 이력을 성공으로 덮어쓰지 않는다. 이 오류만으로 이미 해제한 lease를 재해제하거나 workflow 외부 write를 반복하지 않는다. -
완료 gate는 기존 release → wait/reap → Issue probe 순서에 더해 broker의 정상 종료와 run-owned 소켓 경로 부재를 확인한다.
probe free=true는 lock 해제 증거이지 소켓 권한·삭제 증거가 아니다. Lease 해제와 소켓·handle 정리 실패는 별도로 보고하고 완료로 간주하지 않는다.
Canonical command templates
# <runtime-dir> is pre-created mode 0700 outside every checkout.
python3 <harness-root>/harness/lib/managed_lock_broker.py serve \
--root <harness-root> \
--owner <owner> --repository <repository> --issue <issue-number> \
--role <role> --run-id <run-id> \
--handle <runtime-dir>/<run-id>.handle \
--socket <runtime-dir>/<run-id>.sock \
--ttl-seconds <ttl-seconds>
# After ready JSON; never print/read the token value.
python3 <harness-root>/harness/lib/managed_lock_broker.py request \
--handle <runtime-dir>/<run-id>.handle --action status
python3 <harness-root>/harness/lib/managed_lock_broker.py request \
--handle <runtime-dir>/<run-id>.handle --action acquire_repository
python3 <harness-root>/harness/lib/managed_lock_broker.py request \
--handle <runtime-dir>/<run-id>.handle --action release_repository
python3 <harness-root>/harness/lib/managed_lock_broker.py request \
--handle <runtime-dir>/<run-id>.handle --action renew --ttl-seconds <ttl-seconds>
python3 <harness-root>/harness/lib/managed_lock_broker.py request \
--handle <runtime-dir>/<run-id>.handle --action release
# After wait/reap of the tracked broker process; free=true is mandatory for the Issue resource.
python3 <harness-root>/harness/lib/managed_lock_broker.py probe \
--root <harness-root> \
--owner <owner> --repository <repository> --issue <issue-number> \
--resource issue
Lock 규칙
- Linux OS advisory lock을 사용한다. lock file descriptor는 symlink를 따라가지 않는 방식으로 열고 원자적으로 lock을 획득한다.
- 독립적인 신규 cache 초기화 순서는
bootstrap.lock → repository.lock이다. - Issue workflow가 기존 cache를 사용할 때 broker가 Issue lease를 보유하고, 같은 broker handle의
acquire_repositoryrequest로 repository lease를 중첩한다. 개념적 순서는Issue(broker) → repository(broker)이며 client는 raw descriptor lock을 사용하지 않는다. - Issue 작업에 사용할 cache가 없으면 Issue broker lease 획득 전에 신규 cache 초기화 절차를 완료한다. 초기화 중에는 Issue를 변경하지 않는다. Bootstrap의 repository lock과 bootstrap lock을 모두 해제한 뒤 broker를 시작하고 Issue lease를 획득하며, 이후에는 broker의
Issue → acquire_repository순서를 따른다. Issue 작업의 기준 snapshot은 broker Issue lease 획득 후 읽는다. - Worktree 생성, 등록, lock, 이동, 제거 또는 repair는 broker Issue lease를 유지한 채 같은 broker handle의
acquire_repositoryrequest로 repository lease를 추가 획득해서 수행한다. 일반 파일 편집, 테스트와 commit에는 broker Issue lease만 유지하고 repository lease는 보유하지 않는다. - 같은 repository의 다른 Issue worktree를 검사, 변경 또는 제거하지 않는다. 한 Issue workflow는 자신의 exact Issue worktree만 다룬다.
- 반대 순서로 lock을 획득하거나 이미 잡은 상위 lock을 해제한 뒤 하위 작업을 계속하지 않는다.
- 정상 종료, 오류와 중단 경로 모두에서 broker cleanup protocol을 수행한다: repository lease release, Issue lease release, broker wait/reap, Issue resource
probe --resource issue의free=truereadback. 다른 Issue가 그 사이 repository lock을 획득할 수 있으므로 repository의 순간적 busy 상태만으로 현재 broker cleanup 실패로 판정하지 않는다. - lock 파일은 unlink하지 않는다. 프로세스 종료 시 kernel이 advisory lock을 해제하므로 남아 있는 파일을 stale active lock으로 간주하거나 PID 기준으로 삭제하지 않는다.
신규 cache 초기화
- Authenticated Gitea API adapter로 허용된 role의 stable account identity를 확인한다. MCP면
get_me, 직접 REST면GET /api/v1/user를 사용한다. - exact
owner/repositorymetadata, clone URL, default branch와 branch SHA를 Gitea에서 읽는다. - bootstrap lock을 획득한 뒤 target을 다시 검사한다. 이미 유효한 cache가 있으면 새로 만들지 않고 기존 cache 검증 절차로 전환한다.
- trusted parent 아래 같은 filesystem에 run-unique sibling staging directory를 mode
0700으로 만든다. - staging에
locks/repository.lock을 mode0600으로 만들고 repository lock을 획득한다. bootstrap lock과 repository lock을 모두 유지한 상태에서 clone, ref 검증과 publication을 수행한다. - credential이 필요하면 role profile의 credential을 short-lived child process로만 전달한다. credential을 URL, argv, Git config, log 또는 remote URL에 저장하지 않는다.
- staging 안에
git clone --mirror로repository.git을 만든다. - staging mirror가 bare repository인지,
origin이 exact owner/repository인지, default branch ref가 Gitea SHA와 일치하는지 확인한다. - 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하지 않는다. - 게시 직후 target의 path 유형, 소유권, 권한, bare 상태, remote identity, default branch와 SHA를 다시 검증한다.
- 모든 검증이 끝난 후 repository lock과 bootstrap lock을 역순으로 해제한다.
초기화 실패 시 현재 run이 만든 staging만 안전하게 제거할 수 있다. final target, 다른 run의 staging 또는 정체를 확인할 수 없는 잔여 경로는 자동 삭제하지 않는다.
기존 cache 갱신
- Issue workflow는 broker Issue lease를 유지한 채 같은 handle에
acquire_repository를 request한다. 별도 승인된 non-Issue workflow는 해당 workflow에 명시된 상위 lock과 repository lock 순서를 따른다. - target과
repository.git의 path, ownership, permissions와 bare 상태를 다시 검증한다. origin의 canonical owner/repository identity가 요청과 일치하는지 확인한다. URL 문자열의 credential이나 transport hostname만으로 identity를 판정하지 않는다.- 명시적으로 검증한 origin에서 mirror refs를 prune/fetch한다. 임의 remote 교체, force reset 또는 다른 repository 재사용은 금지한다.
- Gitea에서 default branch와 SHA를 다시 읽고 local ref와 일치하는지 확인한다.
- 일치한 exact SHA를 이번 작업의 pinned source revision으로 기록한다.
- Issue workflow는 같은 handle에
release_repository를 request한다. 별도 non-Issue workflow는 자신의 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에서 구현을 재개하기 전에 다음을 모두 확인한다.
- exact
owner,repository와 Issue 번호가 검증됐고 brokerserve의 ready response로 Issue lease 보유를 확인했다. - Authenticated Gitea API adapter로 role identity, exact repository metadata, Issue body,
updated_at과 구현 승인 상태를 읽었다. - repository cache가 이 문서의 path, ownership, permission, bare, remote identity와 authoritative ref 검증을 통과했다.
- 구현에 사용할 exact base SHA와 그 출처를 결정했다. Discovery pinned revision과 현재 default branch SHA가 다르면 어느 한쪽을 자동 선택하지 않고 변경을 조사해 요구사항과 충돌하지 않는 base를 확정하거나 중단한다.
repository.git에 exact base SHA의 commit object가 존재한다. 문자열 형식이나 object 존재만으로 Gitea 또는 승인된 Discovery에서 유래했다는 검증을 대체하지 않는다.- canonical target path,
state.json과 Git worktree registration의 존재 여부를 함께 조사했다. - 신규 생성이면 target path와 동일 Issue registration이 모두 없어야 한다. 재사용이면 아래의 기존 worktree 검증을 모두 통과해야 한다.
Gate가 끝난 뒤 write 직전에 Issue body, updated_at과 구현 승인 상태를 다시 읽는다. admission snapshot과 다르면 worktree를 생성하거나 기존 worktree를 수정하지 않는다.
Worktree 신규 생성
- Broker Issue lease를 유지한 채 같은 handle에
acquire_repository를 request해 repository lease를 중첩한다. - cache와 base commit을 다시 검증하고
worktrees와 Issue directory를 canonical mode로 생성한 뒤 즉시lstat한다. - final
checkout,state.json과 동일 Issue의 Git worktree registration이 모두 없는지 다시 확인한다. git --git-dir=<repository.git> worktree add --detach <checkout> <full-base-sha>와 동등한 호출로 exact final checkout을 생성한다. branch 생성 option을 사용하지 않는다.- worktree를 Issue 용도로
git worktree lock하고 reason에 repository credential이나 민감정보 없이 Issue 번호를 기록한다. state.json을 state 계약에 따라 원자적으로 게시한다.- 생성 직후 다음을 모두 검증한다.
git rev-parse HEAD가 exact base SHA와 일치한다.git symbolic-ref -q HEAD가 성공하지 않아 detached HEAD임이 확인된다.git status --porcelain이 비어 있다.git rev-parse --git-common-dir가 canonicalrepository.git과 동일하다.git worktree list --porcelain에 exact checkout path가 한 번만 있고 locked 상태다.- checkout path와
state.json의 identity, ownership과 permission이 canonical 계약을 충족한다.
- 모든 검증 후 같은 handle에
release_repository를 request한다. Broker Issue lease는 해당 Issue workflow가 끝날 때까지 유지한다.
Git 명령의 성공 exit만으로 생성 완료를 판정하지 않는다. Git registration, checkout, state와 Issue snapshot을 모두 검증해야 한다.
기존 worktree 재사용
기존 directory 또는 Git registration 중 하나라도 발견되면 신규 생성으로 덮어쓰지 않는다. Broker Issue lease를 유지하고 같은 handle의 acquire_repository request로 얻은 repository lease 아래에서 다음을 모두 확인한다.
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 broker Issue lease 아래에서만 수행한다. 현재 worktree 외의 source, 다른 checkout 또는 installed package를 수정하거나 구현 근거로 사용하지 않는다.
- commit 전후 base SHA, HEAD SHA, staged·unstaged·untracked 상태를 기록한다. detached 상태를 local branch로 자동 전환하지 않는다.
- 명시적으로 승인된 push는 source와 destination을 모두 지정해
HEAD:refs/heads/<approved-branch>형태와 동등하게 수행한다. Canonical cache remote에remote.<name>.mirror=true가 설정돼 있으면 credential을 포함하지 않은 explicit repository URL을 사용하거나git -c remote.<name>.mirror=false push <name> HEAD:refs/heads/<approved-branch>와 동등하게 해당 호출에서만 mirror 동작을 끈다. Persistent remote config는 변경하지 않는다. 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할 수 있다. 이때 broker Issue lease를 유지하고 같은 handle의 acquire_repository request로 repository lease를 중첩한 뒤 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으로 우회하지 않는다.
제거할 때는 broker Issue lease를 유지하고 같은 handle의 acquire_repository request로 repository lease를 중첩한 상태에서 worktree lock을 해제하고 Git operation으로 checkout과 registration을 먼저 제거한다. 제거 전에 아래 verification 계약에 따라 보존할 artifact의 identity, manifest·plan digest와 읽기 가능 여부를 확인한다. 두 대상의 부재를 확인한 뒤 exact state.json과 identity가 확인된 현재 run의 state temporary file만 제거한다. verification/이 남으면 Issue directory를 보존한다. 다른 알 수 없는 파일·directory가 남아 있으면 자동 정리하지 않고 partial로 보고한다. 비어 있는 Issue directory만 제거할 수 있다. 마지막으로 exact checkout, Git registration과 state.json의 부재 및 보존한 verification의 경로·digest가 제거 전과 같음을 다시 확인하고 release_repository를 request한다. Verification이 정상 보존돼 Issue directory가 남는 것은 checkout 제거 실패가 아니다.
Credential 안전
- credential 값은 role profile의
.env에만 저장하고 mode0600을 요구한다. - 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을 더해 모두 다시 읽은 경우에만 완료로 판정한다.
Canonical external Issue verification artifacts
Issue별 검증 실행 기록의 정본은 <harness-root>/repositories/<owner>/<repo>/worktrees/issue-<number>/verification/<run-id>이다. <harness-root>는 실제로 읽은 harness/operations/repository-cache.md에서 결정한다. 제품 source는 형제 checkout/에 있고 verification은 제품 Git tree 밖에 있다. 구현·재작업의 회차별 plan, evidence manifest, command output, RED/GREEN 결과, finding matrix와 head별 provenance를 제품 docs/verification/에 새로 누적하거나 evidence-only commit을 만들지 않는다. 승인된 제품 요구사항이 명시적으로 요구하는 정본 문서와 지속적인 테스트·synthetic fixture·재현 스크립트는 제품에 유지할 수 있다. 기존 파일을 자동 이동·삭제하거나 광범위 .gitignore 변경으로 처리하지 않는다.
- 경로 identity는 인증된 exact owner, repository와 양의 Issue 번호에서 결정한다. 기존 cache identifier 규칙을 따르며
issue-<number>는 선행 0 없는 십진수다.<run-id>는[A-Za-z0-9][A-Za-z0-9_.-]{0,127}형식의 새 식별자다. Stage, source revision 또는 실행을 바꾸면 새 run-id를 사용한다. Dot segment, 절대 artifact file 경로, traversal, symlink와 임의 temporary/artifact root는 거부한다.<owner>/<repo>를 성공한 lookup 결과에 맞춰 자동 정규화하지 않는다. - evidence
run전에 다음 명령으로 live broker lease와 exact harness root·Issue identity를 인증한다:python3 harness/lib/verification_root.py prepare --root <harness-root> --owner <owner> --repository <repository> --issue <issue> --run-id <run-id> --handle <handle>. 이 helper는verification/과<run-id>/를 각각 별도 생성하고, 각 directory가 실행 UID 소유 mode0700인지 생성 직후 다시lstat한다. Symlink, owner·mode 불일치와 path collision은 거부하며, 기존 malformed root를 자동 chmod하거나 repair하지 않는다.Path.mkdir(parents=True, mode=0o700)는 중간 parent가 umask를 따르므로 이 경로에 사용하지 않는다. - Caller가 인증된 Issue identity와 cache/worktree admission을 확인하고 broker Issue lease 아래 위 prepare를 실행한다. Directory는 실행 UID 소유 mode
0700, plan·manifest·output 파일은 실행 UID 소유 mode0600으로 만들고 즉시lstat으로 확인한다. 기존 cache bootstrap, repository lock과 lease 계약은 바꾸지 않는다. Artifact helper는 lease·Gitea identity·Git registration을 대신 인증하거나 directory를 만들고 cache/권한을 복구하지 않는다. repo_root는 canonicalcheckout,artifact_root는 exactverification/<run-id>로 분리한다. Plan의hash_paths는 repository-relative source 경로이고 plan/output/manifest 인자는 run-relative artifact 경로다. 두 namespace는 이름이 같아도 서로 다른 파일이다. Source tree의 tracked·untracked·ignored 파일을 함께 검사하고 evidence 경로 exclusion을 허용하지 않는다. Python은-B또는 allowlist에 선언한PYTHONDONTWRITEBYTECODE=1, pytest는 필요하면-p no:cacheprovider로 checkout 안의 자동 cache 생성을 막는다. 테스트 임시 산출물·dependency environment도 checkout 밖 승인된 경로를 사용한다. Helper가 ignored cache를 자동 삭제하거나 clean 상태로 간주하지 않는다.- 새 실행은
evidence-manifest/v2로 source full SHA, tracked source blob SHA·byte digest, owner/repository/Issue/run-id, plan path·digest, 실제 argv·exit code·환경·bounded output·시간과 claim pointer를 결합한다. Existing manifest는 no-clobber 원자 게시로 덮어쓰지 않는다. 완료 기록은 불변으로 취급하고 실패·재실행도 새 run-id에 보존한다. Helper의validate성공은 local artifact 정합성일 뿐 원격 PR head 또는 provider readback 검증을 대체하지 않는다. - Baseline, integration, precommit과 remote-head 검증은 각 stage에서 검증한 그 commit의 clean source에 결합한다. Dirty 개발 tree는 보존하되 helper가 성공 증거로 게시하지 않는다. Final tracked 제품 산출물을 commit한 뒤 전달할 exact commit에서 검증하고, 실패하면 push하지 않는다. 모든 PR/결과 보고는 같은 immutable record의 command/result를 사용한다.
- Same-UID 배포에서는 인증된 reviewer가 producer 기록을 read-only로 읽는다. 다른 UID나 host에서는 승인된 credential-free read-only export/immutable artifact 전달 수단을 명시한다. 전달 receipt는 repository/Issue/run-id, source SHA, manifest·plan·output digest와 실제 읽기 검증을 묶는다. UID가 다르다고 chmod/chown하거나 임의 sibling path를 허용하지 않는다. 경로 문자열이나 구현자 요약만으로 reviewer-accessible이라고 판단하지 않으며, 접근/무결성/source binding 실패는
evidence_gap이다. - Reviewer는 검증 source를 immutable provider/Git blob에 독립 결합한다. Producer manifest의 테스트 결과는 구현자 증거로 표시하고 reviewer 자체 실행으로 표현하지 않는다. Spec native-review snapshot/result와 운영 자료는 trusted
harness/.runtime에 남겨도 되며 제품 실행 evidence와 혼동하지 않는다. Spec reviewer는 moving implementation checkout을 근거나 test 실행 장소로 재사용하지 않는다. - Checkout 제거, broker release와 verification 폐기는 별개다. Checkout과 state를 제거해도 verification과 그 Issue directory는 보존한다. 보존 기간의 자동 만료나 재귀 삭제를 도입하지 않는다. Artifact 폐기는 별도의 exact 경로 승인, 활성 consumer·delivery/readback 의존성 조사와 digest 기록을 거쳐야 한다. 생성 실패의 미완성 run도 경로 identity가 입증된 현재 run 범위만 정리할 수 있고, 완료 기록은 자동 삭제하지 않는다.
- 인증정보를 사용했다는 사실은 남길 수 있지만 값·토큰·비밀번호·cookie·연결 문자열은 보존하지 않고
[REDACTED]로 기록한다. 새 증거를 만들기 위해 credential을 plan/argv/output에 넣지 않는다. Helper는 알려진 secret·header 유출을 fail-closed하고, 이것을 완전한 sandbox 또는 모든 비밀에 대한 자동 탐지로 과장하지 않는다. - 기존
evidence-manifest/v1은 명시적validate-legacy --repo-root <historical-checkout> --manifest <repository-relative-manifest>로 read-only 호환 확인만 한다. 새run/validate에는 v1·repository-relative evidence 생성 fallback이 없다. Archived replay는 fresh source 실행·원격 head/readback 또는 새 publication 완료를 증명하지 않는다. Migration도 별도 승인 없이 기존 제품 evidence를 수정하지 않는다.