Plan the internal forge delivery boundary

This commit is contained in:
2026-11-01 16:23:58 +00:00
parent ac78e7db73
commit 2700bd18cb

View File

@@ -0,0 +1,202 @@
# Roadmap: separate shared delivery from forge adapters
Status: proposed; implementation has not started.
## Objective and scope
Introduce an internal boundary between shared hosted-release delivery and the
Gitea implementation. The result should support adding another forge through
an adapter while reusing ReleaseKit's release and asset-delivery policy.
ReleaseKit's `Publish` operation validates and publishes source through Git.
Its `Deliver` operation verifies tagged source and delivers release notes and
declared assets to a hosting service. This refactor concerns the latter operation
and its composition through the existing public facade and CLI.
Gitea is the concrete backend for this work. Forgejo, GitHub, GitLab, and other
backends remain future implementation work. The boundary should express the
operations used by the current delivery workflow and allow refinement when a
second backend supplies concrete requirements.
## Current baseline
The source baseline is `ac78e7d`, following the published v0.5.0 module migration
and the subsequent token-file and forbidden-file changes.
- [delivery.go](../../delivery.go) exposes `GiteaDestination` through
`DeliveryRequest.Destination` and converts it into `gitea.Destination`.
- [internal/delivery](../../internal/delivery/delivery.go) resolves source
identity, checks tagged source, reads notes, coordinates builds, and directly
calls `gitea.Client` for release and asset operations. Its result conversion
and failure classification use Gitea-owned internal types.
- [release.go](../../internal/gitea/release.go) combines Gitea HTTP operations
with hosted-tag verification, matching-page acceptance, release creation,
and reconciliation of ambiguous creates.
- [assets.go](../../internal/gitea/assets.go) and
[upload.go](../../internal/gitea/upload.go) combine HTTP operations with
manifests, local-file verification, remote-content comparison, asset
observations, upload sequencing, and retry bookkeeping.
- CLI credential resolution supplies an explicit token from the configured
environment variable or token file. The profile describes a Gitea repository;
it has no forge selector.
The [Gitea integration guide](../integrations/gitea.md) and public Go declarations
document the baseline contracts. Current behavioral tests provide regression
coverage to assess against those contracts. The
[architecture policy](../policy/architecture.md) governs facade ownership and
dependency direction.
## Targeted end state
### Shared delivery responsibilities
Shared internal code owns:
- Captured source identity, local tag/commit checks, committed release notes,
managed or prebuilt artifact coordination, and local artifact verification.
- The existing logical rules for accepting matching hosted content, reporting
conflicts, creating missing content, and reconciling uncertain writes.
- Manifest generation, content comparison, and asset observations used to decide
which declared assets require delivery.
- Aggregation into the existing public delivery outcomes and asset categories.
These operations consume backend-neutral internal values and operations. The
generic delivery workflow has no dependency on `internal/gitea`. The facade or
private composition code constructs the concrete backend and supplies it to
shared delivery.
### Backend responsibilities
The Gitea adapter owns destination validation specific to Gitea, API routes,
authentication headers, request and response formats, pagination, multipart
uploads, attachment downloads, and HTTP-specific error interpretation.
The internal contract supplies the logical operations needed to inspect a
hosted tag, find or create a release, enumerate assets, read asset content, and
upload an asset. Release and asset references carry backend-owned handles;
shared orchestration works with logical identity and content rather than
interpreting Gitea's positive integer IDs.
The contract also carries the evidence needed for existing recovery behavior:
whether a write was attempted, whether its outcome is established or uncertain,
and actionable failure information. Generic code interprets delivery outcomes;
the adapter interprets protocol responses. A logical backend operation may
involve multiple HTTP requests.
The operations and values stay internal under the existing facade policy.
Current Gitea behavior is the concrete compatibility target. This work does
not establish compatibility guarantees for unimplemented forges or a public
plugin protocol.
## Work required
### 1. Define the internal seam and dependency direction
- Inventory the data and operations currently crossing from `internal/delivery`
into `internal/gitea`, including asset identities, inspections, failure
categories, release references, and write-attempt evidence.
- Introduce a small internal contract shared by orchestration and adapters.
Choose its package placement to keep dependencies acyclic. Exact private
package and method names can follow the implementation's responsibilities.
- Represent the observed release data needed by current comparisons without
making Gitea wire structures part of the generic model. Keep numeric-ID and
repository-path-segment validation with the Gitea implementation.
- Put concrete backend construction in composition code. Apply the public
destination choice recorded under Open Decisions.
### 2. Extract shared policy and artifact mechanics
- Move manifest generation and local content verification to shared internal
ownership, reusing the existing artifact package where appropriate.
- Move matching-page decisions, remote-content comparisons, missing-asset
selection, upload sequencing, reconciliation decisions, and result
aggregation out of the Gitea HTTP implementation.
- Preserve the current Gitea behavior for complete note bodies, existing page
titles, stable release state, content conflicts, unrelated attachments,
source-only delivery, and managed or prebuilt binary delivery.
- Preserve current write-history and uncertainty handling. In particular, a
read-only inspection failure and an unresolved write retain their distinct
meanings. Keep existing Gitea reconciliation behavior where the integration
contract specifies it, including the current follow-up lookup behavior.
- Retain the existing reuse of local verification and streamed content hashing
as responsibilities move. Preserve cancellation, stream closure, temporary
output ownership, and cleanup around uploads.
### 3. Adapt Gitea and reconnect the facade and CLI
- Implement the internal contract with the Gitea client. Keep Gitea JSON
decoding, HTTP status interpretation, URL escaping, credential forwarding,
redirect behavior, and upload transport in the adapter.
- Map protocol errors into shared outcomes while retaining useful causes and
Gitea HTTP details. Preserve documented diagnostics and cancellation behavior;
use existing contracts to decide which wording or interactions are meaningful.
- Reconnect `Deliver`, CLI `deliver` and `ci-deliver`, managed builds, and
prebuilt delivery through the same shared workflow and concrete Gitea adapter.
- Continue resolving credentials in the CLI and passing them explicitly. Token
environment/file selection and forbidden-file validation retain their
existing responsibilities.
### 4. Align tests and documentation with ownership
- Retain Gitea HTTP fixture tests for routes, payloads, authentication,
pagination, escaping, redirects, malformed responses, and transport failures.
- Move tests of manifests, content comparison, conflict handling, and result
aggregation to the shared layer as those responsibilities move. Use small
controlled backends where protocol-independent recovery needs an external
boundary; reuse existing behavioral cases and consolidate redundant coverage.
- Retain representative public-facade and CLI workflows proving that Gitea
page-only delivery, binary delivery, tagged retries, and failure reporting
continue to work through the assembled boundary.
- Update the internal inventory and delivery documentation when implementation
lands. Keep Gitea protocol details in the integration guide and shared
orchestration details in internal documentation. Update public GoDoc,
configuration, examples, and migration guidance if the destination API changes.
- Record the accepted architectural boundary in an ADR when its design is
settled, following the [documentation policy](../policy/documentation.md#architecture-decision-records).
## Verification and completion
Follow the [testing policy](../policy/testing.md) and reuse existing tests where
they protect the relevant behavior. Run focused checks while moving
responsibilities, then the complete offline suite and compiled
API/dependency-boundary checks from the [development guide](../development.md#validation).
The final full-suite run also covers the affected executable workflows.
Review the resulting imports and data flow as well as test results: generic
orchestration and artifact policy should consume the internal contract, with
Gitea transport selected through composition. Exercise an internal test backend
at that seam to verify that shared logic can operate independently of the Gitea
client. Keep the test collaborator proportionate to the cases it protects.
Completion means the shared workflow and asset policy have independent internal
ownership, the Gitea adapter implements the boundary, the existing Gitea
contracts pass through that boundary, and documentation describes the resulting
structure. The public API/configuration choice is reflected consistently in
declarations, profiles, examples, and applicable migration guidance.
This is an architectural refactor of the implemented Gitea delivery path.
Support claims and compatibility testing for additional forges belong to the
work that implements those backends. Release preparation and publication
continue under the [existing procedure](../release.md).
## Open Decisions
### Public destination shape during this refactor
**Recommendation:** Retain `GiteaDestination` and the existing profile shape
for this pass, with facade/private composition adapting them into the internal
backend. The public type accurately describes the implemented destination, and
the internal seam can be introduced without a consumer migration. A second
backend will provide concrete evidence for repository addressing, API base
URLs, and any provider selection fields. The tradeoff is that the public API
continues to name Gitea until that follow-up API design is implemented.
**Alternative:** Introduce a facade-owned neutral destination type, such as
`DeliveryDestination`, and an explicit forge selection now, initially dispatching
to Gitea. This makes the consumer-facing shape match the intended multi-forge
direction and allows its migration to happen alongside the internal refactor.
It also adds public/configuration choices before a second backend establishes
their requirements; GitLab project addressing and GitHub API/upload endpoints
may still require later evolution. If selected, define the initial Gitea
selection behavior explicitly and supply migration guidance in the next minor
release, consistent with the project's direct-cutover approach.