release-tool 0.3.0

Configuration-driven release lifecycle for computed-parameter repositories
Documentation
# Domain model

## Release

```rust
enum ReleaseIntent {
    Existing, // 当前 commit 必须已有唯一 remote annotated calendar tag
    New,      // 没有 tag 时选择下一条 YYYY.MM.DD.N
}

struct ReleaseCandidate {
    repository: String,
    commit: String,
    tag: CalendarTag,       // tag 同时就是 cross-language version
    intent: ReleaseIntent,
    tag_already_sealed: bool,
}
```

```text
# identity invariant
(repository, tag, commit) 唯一标识一个 release。

# version invariant
version == tag;不存在 RELEASE_TAG/version_file/bump commit。

# immutability invariant
remote annotated tag 的 peeled commit、artifact identity 与 bytes 不可修改。
```

GitHub Release 的 `target_commitish` 在 tag 已存在时不是 commit proof;`release-tool` 只使用
`git ls-remote` 的 direct/peeled refs 建立 `tag -> commit` invariant。

`Release.version`(calendar tag)与执行工具自身的 SemVer(例如 `release-tool 0.3.0`)是两个
不同概念:前者标识本次 repository release,后者用于 config capability compatibility 和审计。

`New` 表示“允许为当前 commit 创建 tag”,不表示“无条件生成新版本”。当前 commit 已有唯一
有效 tag 时,`New` 复用该 tag,因此 `publish --release` 可安全重跑。

## Target

```rust
trait TargetAdapter {
    // read-only;把 ReleaseCandidate 解析为确定的 artifact identities
    fn resolve(&self, release: &ReleaseCandidate) -> Result<TargetPlan>;

    // build/package;输出与 TargetPlan 完全相同的 inventory 和 local digests
    fn prepare(
        &self,
        plan: &TargetPlan,
        dependencies: &PreparedDependencies,
        staging: &Path,
    ) -> Result<ArtifactManifest>;
}
```

`Target` 回答“从这个 release 生成什么”,不决定 remote write policy。
同一个 `ReleasePlan` 中两个 selected targets 不得声明相同的 remote artifact identity;冲突在
Resolve 结束前失败,而不是靠发布顺序决定谁覆盖谁。
OCI tag 的 collision identity 是 `repository + tag`;platform 是被验证的 manifest 属性,不是
独立 namespace,因此两个 platform 也不能由不同 targets 写入同一个 tag。

```text
DockerArchiveTarget
  input:  image/build/local-check facts
  output: <asset>.tar.xz + <asset>.tar.xz.sha256

MavenReactorTarget
  input:  root POM + selected reactor projects
  output: derived Maven GAV jar/pom identities

OciImageTarget
  input:  one image config + prepared direct dependencies
  output: one OciImage identity + immutable Existing/Reuse/Build metadata
```

Targets 通过 `depends_on` 形成 DAG。Resolve 展开选中 targets 的 transitive dependency closure;
Prepare、Publish、Verify 按稳定拓扑顺序执行。`PreparedDependencies` 让 downstream 看到 dependency
的 artifact/content identity,但不暴露 `Build / Reuse / Existing` execution history。v1 只支持
`oci_image -> oci_image` handoff。

`OciImageTarget` 隐藏 previous candidate discovery 和 decision:project-owned `reuse_check` 决定
exact candidate 可否复用,release-tool 不定义 adopter inputs 或 fingerprint schema。

## Publisher

```rust
trait Publisher {
    fn inspect(&self, plan: &TargetPlan) -> Result<PublicationState>;
    fn publish(&self, manifest: &ArtifactManifest) -> Result<PublicationReceipt>;
    fn verify(&self, manifest: &ArtifactManifest) -> Result<VerificationReport>;
    fn verify_existing(&self, plan: &TargetPlan) -> Result<VerificationReport>;
}
```

`Publisher` 回答“remote 当前是什么状态、能否恢复、如何写入和验证”。它不运行 build,
也不重新解析 version 或 artifact inventory。

```text
PublicationState
├── ABSENT    # 所有 planned identities 都不存在
├── COMPLETE  # identities 完整;仍需 Verify
├── PARTIAL   # publication 未完成,或 target-owned validation 尚未运行
└── INVALID   # metadata/content 与 release identity 冲突
```

OCI current tag 即使存在也先报告 `PARTIAL`,因为 Publisher 不能代替 Target 运行需要 prepared
dependencies 的 `reuse_check`;check 完成后才形成可 Verify 的 `Existing` manifest。

`OciRegistryPublisher` 一次处理一个 `oci_image` target:对 `Build` 执行 push,对 `Reuse` 执行
exact manifest remote-retag,`Existing` 只验证。它执行 write-before/write-after Inspect。
`oci_image` 在配置层隐式选择它,不需要 publisher table/reference。OCI registry 不提供通用
conditional tag create,因此 v1 仍要求 single writer 或 registry immutable-tag enforcement。

## ArtifactManifest 与 receipt

```rust
struct PreparedArtifact {
    identity: ArtifactIdentity,
    path: PathBuf,
    sha256: String,
}

struct ArtifactManifest {
    target: String,
    publisher: String,
    release: ReleaseCandidate,
    artifacts: Vec<PreparedArtifact>,
}
```

发布前会重新计算每个 local artifact 的 SHA-256。文件在 `Prepare` 后发生变化时停止,不把
未记录的 bytes 写入 remote。

```text
PublicationReceipt
├── release-tool version
├── repository / tag / commit
├── target / publisher
├── artifact identities
└── skipped
```

receipt 记录审计 identity;`skipped = true` 只表示 remote write 被省略,不表示省略 Verify。

OCI prepared metadata 记录 destination identity、platform、config digest 与内部 action。Build
dependency 向 downstream 提供已 Inspect 的 local tag;Reuse/Existing dependency 提供
digest-pinned remote reference。Publish 只消费这份 metadata,不重新运行 builder 或 freshness check。