release-tool 0.3.0

Configuration-driven release lifecycle for computed-parameter repositories
Documentation
# State 与 recovery

## Publication decision matrix

```text
State       GitHubReleasePublisher              GitHubMavenPublisher          OciRegistryPublisher
ABSENT      create release / upload assets      deploy exact prepared version Prepare Build/Reuse
COMPLETE    Verify, then idempotent skip         Verify, then idempotent skip  n/a: force Prepare/check
PARTIAL     verify bytes, upload missing         STOP; never redeploy version  Prepare Existing/Reuse
INVALID     STOP                                STOP                          STOP
```

`COMPLETE` 只有在 release 已由 remote annotated tag seal 时才可跳过。若 next candidate version
已有完整 artifacts、却没有对应 tag,工具将其视为 orphan publication 并停止,不自动“认领”。

## Tag state

```text
remote same annotated tag -> expected commit
  success;复用 tag

remote same tag -> other commit / lightweight ref
  stop;不 force、不 delete

push reports failure + remote tag complete
  reconcile 为 success

push reports failure + remote tag absent
  local annotated tag 保留;修复权限/网络后运行同一 command

direct/peeled refs 缺失、歧义或无法读取
  stop;不要自动重试 remote write
```

检查命令:

```bash
git ls-remote --tags origin 'refs/tags/YYYY.MM.DD.N*'
git cat-file -t refs/tags/YYYY.MM.DD.N
git rev-parse 'refs/tags/YYYY.MM.DD.N^{commit}'
```

## GitHub Release partial

```text
1. Inspect expected asset names。
2. 下载所有已存在的 expected assets。
3. 与 Prepared ArtifactManifest SHA-256 比较。
4. 全部一致:只 upload missing names。
5. 任一不一致:INVALID,停止;不使用 --clobber。
```

其它 target 的 asset 可以共存于同一个 GitHub Release,不属于当前 target 的文件不会被删除。

`gh release create` 内部可能经历 draft/upload/publish 多次 API write。发现遗留 draft 时返回
`INVALID` 并停止,避免把“assets 已上传”误判为公开 release 已完成;人工确认前不自动切换 draft。

## GitHub Maven partial

GitHub Packages 的 version/artifact bytes 视为 immutable。一个 version 只存在部分 jar/pom 时,工具
拒绝猜测 deploy 是否能安全续传:

```text
PARTIAL -> stop -> 人工调查 publisher/server state
```

不得删除 package version 后让工具重试;删除/覆盖不属于自动 recovery contract。

## OCI image recovery

OCI target 即使 current tag 存在也进入 Prepare:project-owned `reuse_check` 必须确认它仍满足当前
source/dependency context。未 Seal 的候选版本只要出现 current OCI tag,就视为 orphan publication
并停止,不能把外部 writer 的状态纳入新 release。

```text
Prepare node state
  Existing -> current tag 必须仍指向 prepared manifest/config
  Reuse    -> current absent 时从 previous repository@manifest-digest remote-retag
  Build    -> current absent 时 push 已验证 local candidate

Publish reconciliation
  current exact -> skip node
  current absent -> 执行 prepared action
  current different -> INVALID;不 push、不 retag、不 delete
```

每次写命令失败后先 Inspect current tag;manifest/config/platform 全部符合 prepared metadata 才把
丢失响应视为成功。Existing/Reuse 还要求 remote manifest 与准备时的 exact digest 相同。Build 至少
要求 config digest 与 platform 相同。

OCI Distribution Spec 的 conditional HTTP push 是 registry 可选能力;v1 Docker CLI adapter 不协商
它。write-before/write-after Inspect 可以发现既有冲突与写后不一致,但不能证明两个并行 writers
之间从未发生覆盖;同一 release 必须由 single writer 串行执行,或依赖 registry-side
immutable tags。Doctor 把该能力显示为 `UNVERIFIABLE`。

## Response lost after remote write

```text
write command/network error
  -> Inspect remote identity
  -> COMPLETE
       -> Verify exact bytes
       -> success receipt
  -> ABSENT/PARTIAL/INVALID/unreadable
       -> return original write error + observed state
```

这条规则同时用于 tag push、GitHub Release write、Maven deploy 和 OCI push/remote-retag。

## Safe retry

```bash
# 先 read-only 确认 candidate 与 remote state
release-tool plan --release
release-tool doctor

# 使用原命令;不要手工递增 tag
release-tool publish --release
```

只有错误明确指出 local-only tag retained 时,local tag 才是可自动恢复的中间状态。其它不一致先
停止并人工检查。