Add shared managed lock broker
This commit is contained in:
@@ -57,16 +57,60 @@ Repository cache 경로는 고정된 machine-local 절대경로나 현재 작업
|
||||
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를 명시적으로 승인한 경우에만 수행하고 즉시 재검증한다.
|
||||
|
||||
## 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 `0700` runtime directory를 미리 만들고, run-unique handle과 socket path를 사용한다. Handle file은 mode `0600`이고 socket은 mode `0600`이며 둘 다 symlink가 아니어야 한다.
|
||||
- `serve`는 tracked background process로 시작하고 ready JSON을 기다린 뒤에만 Issue 작업을 시작한다. Client는 raw token을 직접 read/log하지 않으며, broker public `request` API가 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에 raw `flock ... sleep` 또는 별도 lock holder를 사용하지 않는다.
|
||||
- TTL 만료 전에 `renew`를 request한다. Broker crash 뒤 kernel이 advisory lock을 해제해도 stale handle은 진단 정보일 뿐이며 `probe`가 authoritative하다.
|
||||
- 정상, 오류와 cancel 경로는 동일한 cleanup을 수행한다: 보유 중이면 `release_repository`, `release`, broker process wait/reap, 그리고 Issue `probe`를 실행해 `free=true`를 요구한다. 다른 Issue workflow가 합법적으로 repository lock을 먼저 획득할 수 있으므로 완료 gate는 Issue resource를 검사하고, 종료한 broker process가 자신의 repository FD를 더 이상 보유하지 않음을 wait/reap으로 증명한다. 이 순서를 검증하기 전에는 completed/log/report를 주장하지 않는다. Lock 파일은 절대 unlink하지 않는다.
|
||||
|
||||
### Canonical command templates
|
||||
|
||||
```sh
|
||||
# <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를 사용할 때의 순서는 `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은 보유하지 않는다.
|
||||
- Issue workflow가 기존 cache를 사용할 때 broker가 Issue lease를 보유하고, 같은 broker handle의 `acquire_repository` request로 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_repository` request로 repository lease를 추가 획득해서 수행한다. 일반 파일 편집, 테스트와 commit에는 broker Issue lease만 유지하고 repository lease는 보유하지 않는다.
|
||||
- 같은 repository의 다른 Issue worktree를 검사, 변경 또는 제거하지 않는다. 한 Issue workflow는 자신의 exact Issue worktree만 다룬다.
|
||||
- 반대 순서로 lock을 획득하거나 이미 잡은 상위 lock을 해제한 뒤 하위 작업을 계속하지 않는다.
|
||||
- 정상 종료, 오류와 중단 경로 모두에서 descriptor를 닫아 lock을 해제한다.
|
||||
- 정상 종료, 오류와 중단 경로 모두에서 broker cleanup protocol을 수행한다: repository lease release, Issue lease release, broker wait/reap, Issue resource `probe --resource issue`의 `free=true` readback. 다른 Issue가 그 사이 repository lock을 획득할 수 있으므로 repository의 순간적 busy 상태만으로 현재 broker cleanup 실패로 판정하지 않는다.
|
||||
- lock 파일은 unlink하지 않는다. 프로세스 종료 시 kernel이 advisory lock을 해제하므로 남아 있는 파일을 stale active lock으로 간주하거나 PID 기준으로 삭제하지 않는다.
|
||||
|
||||
## 신규 cache 초기화
|
||||
@@ -87,13 +131,13 @@ Repository cache 경로는 고정된 machine-local 절대경로나 현재 작업
|
||||
|
||||
## 기존 cache 갱신
|
||||
|
||||
1. 필요한 상위 lock을 유지한 채 repository lock을 획득한다.
|
||||
1. Issue workflow는 broker Issue lease를 유지한 채 같은 handle에 `acquire_repository`를 request한다. 별도 승인된 non-Issue workflow는 해당 workflow에 명시된 상위 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을 해제한다.
|
||||
7. Issue workflow는 같은 handle에 `release_repository`를 request한다. 별도 non-Issue workflow는 자신의 repository lock을 명시된 순서로 해제한다.
|
||||
|
||||
## Issue worktree 모델
|
||||
|
||||
@@ -131,7 +175,7 @@ Repository cache 경로는 고정된 machine-local 절대경로나 현재 작업
|
||||
|
||||
Worktree를 신규 생성하거나 기존 worktree에서 구현을 재개하기 전에 다음을 모두 확인한다.
|
||||
|
||||
1. exact `owner`, `repository`와 Issue 번호가 검증됐고 Issue lock을 획득했다.
|
||||
1. exact `owner`, `repository`와 Issue 번호가 검증됐고 broker `serve`의 ready response로 Issue lease 보유를 확인했다.
|
||||
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를 확정하거나 중단한다.
|
||||
@@ -143,7 +187,7 @@ Gate가 끝난 뒤 write 직전에 Issue body, `updated_at`과 구현 승인 상
|
||||
|
||||
## Worktree 신규 생성
|
||||
|
||||
1. Issue lock을 유지한 채 repository lock을 획득한다.
|
||||
1. Broker Issue lease를 유지한 채 같은 handle에 `acquire_repository`를 request해 repository lease를 중첩한다.
|
||||
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을 사용하지 않는다.
|
||||
@@ -156,13 +200,13 @@ Gate가 끝난 뒤 write 직전에 Issue body, `updated_at`과 구현 승인 상
|
||||
- `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가 끝날 때까지 유지한다.
|
||||
8. 모든 검증 후 같은 handle에 `release_repository`를 request한다. Broker Issue lease는 해당 Issue workflow가 끝날 때까지 유지한다.
|
||||
|
||||
Git 명령의 성공 exit만으로 생성 완료를 판정하지 않는다. Git registration, checkout, state와 Issue snapshot을 모두 검증해야 한다.
|
||||
|
||||
## 기존 worktree 재사용
|
||||
|
||||
기존 directory 또는 Git registration 중 하나라도 발견되면 신규 생성으로 덮어쓰지 않는다. Issue lock을 유지하고 repository lock 아래에서 다음을 모두 확인한다.
|
||||
기존 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 상태다.
|
||||
@@ -176,7 +220,7 @@ Dirty worktree, untracked 파일 또는 base 이후 commit은 진행 중 구현
|
||||
|
||||
## Worktree 작업과 delivery
|
||||
|
||||
- 일반 편집, 테스트와 commit은 exact Issue lock 아래에서만 수행한다. 현재 worktree 외의 source, 다른 checkout 또는 installed package를 수정하거나 구현 근거로 사용하지 않는다.
|
||||
- 일반 편집, 테스트와 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 변경은 각각 별도 승인이 필요하다.
|
||||
@@ -184,7 +228,7 @@ Dirty worktree, untracked 파일 또는 base 이후 commit은 진행 중 구현
|
||||
|
||||
## 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 삭제를 먼저 수행하지 않는다.
|
||||
현재 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로 보고한다.
|
||||
|
||||
@@ -198,7 +242,7 @@ Dirty worktree, untracked 파일 또는 base 이후 commit은 진행 중 구현
|
||||
|
||||
정상 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가 모두 없는지 다시 검증한다.
|
||||
제거할 때는 broker Issue lease를 유지하고 같은 handle의 `acquire_repository` request로 repository lease를 중첩한 상태에서 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가 모두 없는지 다시 검증하고 `release_repository`를 request한다.
|
||||
|
||||
## Credential 안전
|
||||
|
||||
|
||||
Reference in New Issue
Block a user