diff --git a/docs/roadmap/forge-boundary.md b/docs/roadmap/forge-boundary.md new file mode 100644 index 0000000..f05bfeb --- /dev/null +++ b/docs/roadmap/forge-boundary.md @@ -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.