261 lines
18 KiB
Markdown
261 lines
18 KiB
Markdown
---
|
|
name: gitea-release
|
|
description: 승인된 Gitea 버전을 준비·태깅하고 산출물을 게시한다.
|
|
version: 0.3.1
|
|
author: genichin, Hermes Agent
|
|
license: MIT
|
|
platforms: [linux]
|
|
metadata:
|
|
hermes:
|
|
tags: [gitea, release, worktree, tag, artifact]
|
|
related_skills: [gitea-repository-cache-bootstrap]
|
|
---
|
|
|
|
# Gitea Release
|
|
|
|
Gitea repository cache를 사용해 버전 준비, 검증, 태깅과 프로젝트별 산출물 게시를 수행한다. Gitea 인증과 readback은 [Gitea authenticated access 운영 지침](../../../../operations/gitea-access.md), cache 규칙은 [Repository cache 운영 지침](../../../../operations/repository-cache.md)을 정본으로 사용한다.
|
|
|
|
## 사용 시점
|
|
|
|
- exact `owner/repository`의 새 버전 준비를 요청받았을 때
|
|
- 준비된 commit을 태깅하고 승인된 산출물을 게시하도록 요청받았을 때
|
|
- 기존 릴리즈의 tag, commit과 게시 산출물을 검증할 때
|
|
|
|
Issue 구현, 기존 tag 이동, artifact 덮어쓰기 또는 deployment에는 사용하지 않는다. 버전이 생략됐고 사용자가 이 작업 유형에 대해 다음 버전 결정을 위임했다면 authoritative tag·release·version source와 변경 범위를 근거로 다음 버전을 선택한다. 근거가 충돌하거나 tag convention이 확인되지 않으면 추측하지 않는다.
|
|
|
|
## 책임 분리
|
|
|
|
### 공통 릴리즈 처리
|
|
|
|
이 스킬이 담당한다.
|
|
|
|
- repository cache 검증과 fetch
|
|
- release lock과 worktree 관리
|
|
- 버전 변경 파일 제한
|
|
- 간단한 준비 검증
|
|
- local commit 생성과 commit tree 검증
|
|
- 승인된 commit에 tag 생성·push
|
|
- 프로젝트별 게시 절차 호출
|
|
- 원격 tag와 artifact readback
|
|
- 성공한 worktree 정리
|
|
|
|
### 프로젝트별 처리
|
|
|
|
Target repository의 `.gitea/release.yaml` contract가 담당한다.
|
|
|
|
- version source와 함께 변경할 lockfile
|
|
- test, lint와 build 명령
|
|
- artifact 종류와 이름
|
|
- 대상 platform과 build context
|
|
- registry, package namespace와 인증 secret 이름
|
|
- smoke test와 게시 명령
|
|
- digest, checksum 또는 provenance 검증
|
|
|
|
기존 workflow, build metadata와 문서는 contract 작성·검토의 근거일 뿐 게시 정본이 아니다. Contract가 없거나 모호하면 확인된 사실만 보고하고 준비 commit 또는 게시를 시작하지 않는다. 이미지 이름, registry, credential, 명령 또는 artifact를 추측하지 않는다.
|
|
|
|
## Repository release contract
|
|
|
|
Repository root의 `.gitea/release.yaml`을 프로젝트별 릴리즈 정본으로 사용한다. Safe YAML mapping으로 읽고 duplicate key, alias·custom tag, 알 수 없는 schema·field·adapter·artifact type을 거부한다. 현재 지원 schema는 exact `gitea-agent/release/v1`이다.
|
|
|
|
### 공통 계약
|
|
|
|
- `version.sources`는 version을 읽고 변경할 repository-relative path, format과 selector를 선언한다. 현재 지원 format은 dotted key를 지정하는 `toml`과 top-level 문자열 대입 이름을 지정하는 `python-assignment`다.
|
|
- `version.lockfiles`는 version source와 함께 갱신·검증할 lockfile을 선언한다. 현재는 `uv.lock`의 exact package entry만 지원한다.
|
|
- `version.tag_template`은 `{version}`으로 exact tag를 만든다.
|
|
- `prepare.runtime`은 검증 runtime을, `prepare.commands[].argv`는 순서대로 실행할 명령과 인수를 선언한다.
|
|
- `artifacts[]`는 `type`, build 또는 source 처리, smoke, publish와 authoritative verify 항목을 선언한다.
|
|
- Credential에는 secret 값이 아니라 active profile에서 읽을 환경변수 이름만 기록한다.
|
|
|
|
`argv`는 shell command가 아니다. 각 항목을 argument 배열로 직접 실행하고 glob, command substitution, redirection, pipe, shell variable expansion을 적용하지 않는다. 빈 argument, 문자열이 아닌 argument 또는 실행 파일이 repository 계약과 무관하면 거부한다.
|
|
|
|
지원 placeholder는 `{version}`, `{tag}`, `{commit}`, `{run_id}`뿐이다. `{commit}`은 prepared full SHA, `{run_id}`는 이번 local 실행에만 사용하는 비밀이 아닌 충돌 방지 식별자다. 다른 placeholder, 재귀 치환 또는 치환 결과의 shell 재해석을 금지한다.
|
|
|
|
Contract의 모든 path와 build context는 release worktree 내부의 non-symlink 경로로 해석돼야 한다. `..`, absolute path, symlink escape와 worktree 밖 script를 거부한다. Contract 자체가 symlink이면 중단한다.
|
|
|
|
### Artifact adapter
|
|
|
|
- `type: container`는 build adapter, exact platform·context·Dockerfile, smoke checks, registry·repository, credential 이름, tags, overwrite 정책과 readback 항목을 요구한다. Build한 하나의 local image를 smoke test하고 그 동일 image digest만 게시한다. 재빌드 결과를 대신 게시하지 않는다.
|
|
- `type: source`는 container build를 실행하지 않는다. Tag만으로 제공되는 source archive 또는 별도 승인된 Gitea Release·attachment를 `publish`에 명시하고 checksum readback을 요구한다.
|
|
|
|
지원하지 않는 artifact type이나 adapter는 구현을 추측하지 않고 중단한다. `latest`, 암묵적 channel, 계약에 없는 tag·attachment 또는 default registry를 추가하지 않는다.
|
|
|
|
### Contract 고정
|
|
|
|
Contract를 읽은 exact bytes의 SHA-256, schema와 path를 `state.json`에 기록한다. `.gitea/release.yaml`은 prepared commit tree에 포함돼야 한다. 준비 검증, tag 생성, build와 publish 직전에 prepared tree의 contract hash를 다시 계산해 기록값과 비교한다. 차이가 있으면 이전 검증과 prepared 상태를 무효화한다.
|
|
|
|
## 승인 계약
|
|
|
|
### 준비 승인
|
|
|
|
버전 변경과 local commit을 시작하려면 다음이 필요하다.
|
|
|
|
- exact `owner/repository`
|
|
- exact version과 tag, 또는 버전 생략 시 다음 버전 결정을 위임한 승인
|
|
- contract-declared version source와 lockfile 변경에 대한 릴리즈 요청 범위
|
|
|
|
위임된 버전 선택은 기존 remote tag·release, 모든 version source·lockfile, tag template, 최신 release 이후 변경과 repository versioning 관례를 순서대로 조사한다. 선택한 version·tag와 근거를 `state.json`에 기록하고 publication gate에서 부재를 다시 확인한다. 명시된 version·tag는 그대로 보존하며 정규화하거나 교체하지 않는다.
|
|
|
|
### 게시 승인
|
|
|
|
Tag 또는 artifact를 외부에 게시하려면 다음이 필요하다.
|
|
|
|
- exact `owner/repository`
|
|
- exact version과 tag 또는 위임 규칙으로 확정해 기록한 version과 tag
|
|
- 검증과 authoritative default-branch readback으로 확정한 prepared full commit SHA
|
|
- repository contract가 선언한 exact artifact와 게시 대상
|
|
|
|
Repository 전체 릴리즈 요청은 contract-declared immutable tag와 artifact 게시까지 승인한다. 다만 mutation 직전에 full SHA, tag와 artifact target을 시스템이 확정·기록해야 한다. 브랜치명, 축약 SHA, `HEAD`, moving tag 또는 contract 밖의 unstated default는 게시 근거가 아니다.
|
|
|
|
## Release worktree 모델
|
|
|
|
Repository마다 동시에 하나의 release workflow만 허용한다.
|
|
|
|
```text
|
|
<harness-root>/repositories/<owner>/<repository>/
|
|
├── locks/release.lock
|
|
└── worktrees/release/
|
|
├── state.json
|
|
└── checkout/
|
|
```
|
|
|
|
- `release.lock`을 전체 workflow 동안 유지한다.
|
|
- Cache fetch, worktree 등록 변경과 제거에는 `release.lock → repository.lock` 순서로 획득한다.
|
|
- Worktree는 확인된 원격 default-branch full SHA의 detached HEAD로 생성하고 lock한다.
|
|
- 기존 release worktree를 reset, clean, rebase, checkout, force-remove 또는 덮어쓰지 않는다.
|
|
- 실패한 release worktree는 증거 보존을 위해 자동 제거하지 않는다.
|
|
|
|
`state.json`에는 최소한 schema `gitea-agent.release-worktree/v1`, repository identity, base SHA, requested version·tag, prepared commit SHA, contract path·schema·SHA-256과 단계 상태를 기록한다. Credential 이름은 기록할 수 있지만 credential 값, PID 또는 secret 값은 기록하지 않는다.
|
|
|
|
State는 선택 사항인 로그가 아니라 stage admission gate다. Worktree 생성 직후 원자적으로 만들고 version 결정, 준비 검증, prepared commit, PR merge readback, merged-SHA 재검증, tag readback, artifact readback마다 다음 external write 전에 갱신한다. 필수 field가 없거나 현재 worktree·remote state와 불일치하면 merge, tag 또는 artifact 게시를 시작하지 않는다. 성공 readback을 state에 durable하게 기록하기 전에는 worktree와 증거를 정리하지 않는다.
|
|
|
|
## 절차
|
|
|
|
### 1. Target과 cache 확인
|
|
|
|
1. 요청의 repository, version, tag와 승인 범위를 추출한다.
|
|
2. Canonical authenticated Gitea API adapter로 identity, repository metadata, default branch와 SHA를 읽는다.
|
|
3. `operations/repository-cache.md`를 새로 읽고 실제 문서 경로에서 `<harness-root>`와 repository cache root를 계산·검증한 뒤 cache를 검증하거나 초기화한다.
|
|
4. Release lock 아래 repository lock을 획득해 exact origin을 prune/fetch한다.
|
|
5. Gitea default-branch SHA와 local mirror ref 일치를 확인한다.
|
|
|
|
완료 조건: authoritative repository identity와 local cache가 같은 full SHA를 가리킨다.
|
|
|
|
### 2. Release worktree 생성
|
|
|
|
1. 기존 release worktree, Git registration과 state를 함께 조사한다.
|
|
2. 대상이 모두 없을 때만 exact default-branch SHA에서 detached worktree를 만든다.
|
|
3. Worktree를 release 용도로 lock하고 `state.json`을 원자적으로 기록한다.
|
|
4. HEAD, detached 상태, common Git directory와 clean 상태를 다시 확인한다.
|
|
|
|
완료 조건: release worktree가 확인된 base SHA에서 clean·detached·locked 상태다.
|
|
|
|
### 3. 프로젝트 계약 조사
|
|
|
|
1. Release worktree root의 exact `.gitea/release.yaml` 존재, regular-file 유형과 경로 경계를 확인한다.
|
|
2. Safe YAML로 읽고 exact schema, 허용 field, argument 배열, placeholder와 path를 검증한다.
|
|
3. `version.sources`와 `version.lockfiles`를 `read_file`로 읽어 현재 선언 version이 서로 일치하는지 확인한다. 요청된 exact version과 다르면 stage 4에서 변경할 exact path와 selector를 version allowlist로 고정한다.
|
|
4. `tag_template` 결과가 요청된 exact tag와 일치하는지 확인한다.
|
|
5. `prepare.commands`, artifact type, build·smoke·publish adapter, exact target과 verify 항목을 분리한다.
|
|
6. Contract bytes의 SHA-256을 계산해 path·schema와 함께 `state.json`에 기록한다.
|
|
7. Credential 값은 읽거나 출력하지 않은 채 선언된 이름이 active profile에 존재하고 exact target에 사용할 수 있는지만 확인한다.
|
|
8. 기존 workflow, build metadata와 release 문서는 contract와 충돌하는 stale 지시가 없는지 검토한다. 충돌하면 contract를 임의 해석하지 않고 중단한다.
|
|
|
|
완료 조건: 검증된 contract hash에 변경 파일 allowlist, exact 준비 명령, artifact adapter, 게시 대상과 authoritative readback이 모두 고정돼 있다.
|
|
|
|
### 4. 버전 변경과 준비 commit
|
|
|
|
1. 승인된 exact version만 contract의 `version.sources`와 `version.lockfiles` allowlist에 반영한다.
|
|
2. Contract 변경이나 다른 non-version 변경은 사용자에게 명시적으로 승인된 exact path만 별도 allowlist에 포함한다.
|
|
3. `prepare.commands`를 선언 순서대로, worktree root에서 shell 없이 exact `argv`로 실행한다. Exit code와 비민감 결과를 기록한다.
|
|
4. 수정·미추적 파일 전체를 검사하고 두 allowlist 밖의 변경이 있으면 중단한다.
|
|
5. Exact path만 stage하고 staged diff, secret, 삭제와 whitespace 오류를 검토한다.
|
|
6. Non-amended local commit을 생성한다.
|
|
7. Commit의 parent, tree, version·tag, contract hash 일치와 clean worktree를 검증한다.
|
|
8. `state.json`에 prepared full SHA, contract hash와 검증 결과를 원자적으로 기록한다.
|
|
|
|
완료 조건: 승인된 exact 변경만 포함하고 contract와 준비 검증이 고정된 local prepared commit이 존재한다.
|
|
|
|
### 5. 게시 gate
|
|
|
|
다음을 모두 확인한다.
|
|
|
|
- 게시 승인이 exact prepared full SHA를 포함한다.
|
|
- Gitea와 local에서 동일 tag가 존재하지 않는다.
|
|
- 동일 artifact version 또는 image tag가 존재하지 않는다.
|
|
- 필요한 credential과 target이 확인됐다.
|
|
- 필수 test와 check가 exact prepared commit에서 성공했다.
|
|
- Worktree HEAD와 tree가 준비 검증 이후 바뀌지 않았다.
|
|
- Prepared tree의 `.gitea/release.yaml` hash가 `state.json`과 일치한다.
|
|
- Contract의 모든 adapter, target, tag와 verify 항목이 지원된다.
|
|
|
|
하나라도 불명확하면 외부 상태를 변경하지 않는다.
|
|
|
|
Prepared 변경을 default branch에 전달해야 하면 publication gate 전에 explicit release branch로 push하고 PR의 exact base/head와 changed paths를 readback한 뒤 merge한다. Merge 후 authoritative default branch full SHA를 다시 읽고 mirror를 fetch한다. Candidate worktree를 없애고 다시 만드는 복합 shell command 대신 각 단계의 preflight, 단일 mutation과 readback을 분리한다. Clean candidate가 remote branch로 보존됐음을 확인한 다음 exact merged SHA의 detached worktree로 전환하고 source·contract·artifact smoke gate를 다시 실행한다. Pre-merge draft image는 게시하지 않는다.
|
|
|
|
### 6. Tag와 프로젝트별 게시
|
|
|
|
1. 승인된 exact tag를 prepared commit에 생성한다.
|
|
2. Explicit remote URL과 refspec으로 신규 tag만 non-force push한다.
|
|
3. Gitea readback으로 remote tag target이 prepared SHA와 일치하는지 확인한다.
|
|
4. Prepared tree에서 contract hash를 다시 검증한다.
|
|
5. `type: container`이면 contract의 exact platform·context·Dockerfile과 label로 한 번 build하고 선언된 smoke checks를 같은 local image에 실행한다.
|
|
6. Registry credential은 선언된 환경변수에서만 읽고 URL, argv, Git config, state 또는 로그에 넣지 않는다. Redirect를 거부하고 exact registry origin에만 보낸다.
|
|
7. `overwrite: false`이면 모든 선언 remote image tag의 부재를 확인한 뒤 검증한 동일 local image를 선언된 tag로 게시한다.
|
|
8. `type: source`이면 container 명령을 건너뛰고 contract와 별도 게시 승인에 선언된 Gitea Release 또는 attachment만 게시한다.
|
|
9. 승인된 artifact와 immutable commit-derived tag만 게시한다.
|
|
|
|
`latest`, 추가 channel, 추정 artifact 또는 기존 tag를 게시·이동·덮어쓰지 않는다.
|
|
|
|
### 7. 권위 있는 검증
|
|
|
|
- Gitea remote tag target을 다시 읽는다.
|
|
- Contract의 `verify` 항목을 모두 authoritative target에서 읽는다.
|
|
- Container는 최소 digest, platform과 prepared SHA revision label을 검증한다.
|
|
- Source attachment는 exact filename, size와 checksum을 검증한다.
|
|
- Artifact provenance가 prepared SHA와 일치하는지 확인한다.
|
|
- 여러 tag를 게시했다면 모두 같은 승인 artifact를 가리키는지 확인한다.
|
|
- Gitea Release가 별도 승인됐다면 target, metadata와 attachments를 다시 읽는다.
|
|
|
|
완료 조건: remote tag와 모든 승인 산출물이 prepared SHA에 연결되고 immutable identifier로 확인된다.
|
|
|
|
### 8. 정리
|
|
|
|
모든 readback이 성공하고 worktree가 clean하며 prepared commit이 remote tag로 보존된 경우에만 release worktree를 제거한다.
|
|
|
|
1. Release lock과 repository lock을 유지한다.
|
|
2. Worktree lock을 해제하고 Git operation으로 registration과 checkout을 제거한다.
|
|
3. `state.json`과 빈 release directory만 제거한다.
|
|
4. Worktree와 registration 부재를 확인한 뒤 lock을 역순으로 해제한다.
|
|
|
|
Partial failure, dirty state, 미게시 commit 또는 불확실한 readback에서는 정리하지 않는다.
|
|
|
|
## 실패 처리
|
|
|
|
- Version, tag, commit, artifact 또는 target을 추측하지 않는다.
|
|
- `.gitea/release.yaml`이 없거나 schema·hash·path·adapter 검증에 실패하면 준비 commit과 게시를 시작하지 않는다.
|
|
- Workflow나 README를 contract의 대체 정본으로 사용하지 않는다.
|
|
- 기존 tag를 force-update, 삭제 또는 재생성하지 않는다.
|
|
- 기존 artifact를 덮어쓰거나 삭제하지 않는다.
|
|
- 실패한 build를 임의 변경으로 수리하지 않는다.
|
|
- External write 실패를 blind retry하지 않는다.
|
|
- Registry push가 timeout 또는 응답 유실로 끝나면 모든 intended manifest tag와 pagination이 완료된 package-version collection을 먼저 readback한다. 전부 부재할 때만 동일 local image ID의 push를 재시도하고, 일부만 존재하면 digest reconciliation 없이는 재시도하지 않는다.
|
|
- Non-root container smoke에서 `--tmpfs`가 image의 `/data` ownership을 가린다는 점을 고려한다. Tmpfs를 쓰면 expected UID/GID/mode를 mount option에 명시하고, 그렇지 않으면 image 결함이 아닌 false permission failure로 판정될 수 있다.
|
|
- 승인 UI가 필요한 destructive command에 fetch, worktree 제거·생성, credential operation과 검증을 한 호출로 묶지 않는다. 승인 timeout 후에는 명령이 실행되지 않았다고 가정하지 말고 worktree registration, checkout HEAD와 mirror ref를 read-only로 reconcile한 뒤 사용자 승인으로 재개한다.
|
|
- 부분 성공 상태와 안전한 재개 지점을 보존하고 사용자 결정을 요청한다.
|
|
|
|
## 검증 및 보고
|
|
|
|
완료 보고에는 다음을 포함한다.
|
|
|
|
- repository와 authenticated identity
|
|
- base, prepared와 remote tag full SHA
|
|
- exact version과 tag
|
|
- 변경 파일과 commit
|
|
- 실행한 test·build·smoke 명령의 실제 결과
|
|
- 게시 artifact의 exact 이름과 immutable digest 또는 checksum
|
|
- 수행한 외부 write와 authoritative readback
|
|
- worktree 정리 또는 보존 상태
|
|
- blocker와 partial failure
|
|
|
|
명령 성공만으로 릴리즈 완료를 주장하지 않는다. Remote tag와 모든 승인 산출물의 fresh readback이 일치할 때만 완료다.
|