From 44823ce323d334aa24b1c75391f06f4461e8722a Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 1 Nov 2026 16:37:50 +0000 Subject: [PATCH] Plan the neutral forge delivery refactor --- docs/roadmap/forge-boundary.md | 129 +++------- docs/roadmap/implementation.md | 426 +++++++++++++++++++++++++++++++++ 2 files changed, 458 insertions(+), 97 deletions(-) create mode 100644 docs/roadmap/implementation.md diff --git a/docs/roadmap/forge-boundary.md b/docs/roadmap/forge-boundary.md index 1ba3aea..473c267 100644 --- a/docs/roadmap/forge-boundary.md +++ b/docs/roadmap/forge-boundary.md @@ -1,6 +1,9 @@ # Roadmap: separate shared delivery from forge adapters -Status: proposed; implementation has not started. +Status: accepted; implementation has not started. + +The [implementation plan](implementation.md) owns stages, concrete design +choices, task sequencing, and verification. ## Objective and scope @@ -50,9 +53,9 @@ dependency direction. ### Neutral public destination and explicit forge selection -Replace `GiteaDestination` with a facade-owned neutral destination type, such as -`DeliveryDestination`, and require explicit forge selection in the public API -and delivery configuration. Gitea is the sole supported selection initially. +Replace `GiteaDestination` with a facade-owned neutral destination type and +require explicit forge selection in the public API and delivery configuration. +Gitea is the sole supported selection initially. An omitted, blank, or unsupported selection produces a validation error; Gitea is selected explicitly rather than inferred from a default or destination URL. @@ -101,101 +104,33 @@ 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 +## Behavioral scope and compatibility -### 1. Define the internal seam and dependency direction +The refactor preserves the existing Gitea delivery contracts for complete note +bodies, retained existing titles, stable release state, content conflicts, +unrelated attachments, additive retries, and source-only or binary delivery. +Read-only inspection failures and unresolved writes retain their distinct +outcomes. Recovery retains write history, including confirmed progress after +an ambiguous create. The current integration guide owns the concrete protocol +and follow-up lookup behavior. -- 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, using the explicitly - selected forge from the neutral public destination. +Local verification reuse, streaming, cancellation, stream closure, temporary +build ownership, and upload cleanup remain part of the affected workflow. +Credential environment/file selection and forbidden-file validation retain +their existing responsibilities. Backend-specific address validation stays +with that backend. -### 2. Extract shared policy and artifact mechanics +The public destination replacement and required forge selection are deliberate +API and configuration changes for the next minor release. Migration guidance +describes the consumer edits under the project's direct-cutover approach. +Current contract documents and examples change with implementation; historical +release notes retain their original contents. -- 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. +Completion means shared delivery and asset policy have independent internal +ownership, Gitea implements the backend boundary, existing Gitea contracts work +through it, and the neutral public destination and explicit selection are +consistent across the API, configuration, examples, and documentation. -### 3. Adapt Gitea and reconnect the facade and CLI - -- Replace `GiteaDestination` with the neutral facade-owned destination and add - required forge selection to the public API and delivery profile. Validate - omitted, blank, and unsupported selections without assuming Gitea. -- 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 with explicit Gitea selection. - Cover missing, blank, and unsupported selections in destination validation. -- 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 ReleaseKit's own profile for the neutral - destination and explicit Gitea selection. Supply migration guidance in the - next minor release for the public type replacement and profile changes, - consistent with the project's direct-cutover approach. -- 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 neutral public destination and required forge selection are -reflected consistently in declarations, profiles, examples, and 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). +Additional forge implementations and compatibility claims belong to later work. +Release preparation and publication continue under the +[existing procedure](../release.md). diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..2c4c152 --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,426 @@ +# Implementation plan: neutral forge delivery boundary + +Status: ready for implementation; all stages are pending. + +## Purpose and execution + +Implement the target in the [feature roadmap](forge-boundary.md). That document +owns scope and user choices; this plan owns concrete design and ordered work. +The baseline is `71746f4`: Gitea delivery works, token files and +`forbidden_files` are implemented, and the neutral destination is planned. + +Execute Stage 1 through Stage 9 in order as one end-to-end sprint. Each stage +is a focused Luna implementation task. Intermediate stages may leave callers +or tests uncompilable while responsibilities move. Continue without an +intermediate release, pilot, external action, or human review. Run focused +checks when the affected packages compile; Stage 9 owns final assembled +verification. Record completion here and follow the +[agent policy](../policy/agents.md) for commits and validation reporting. + +Read the [development guide](../development.md) and applicable policies. Use +the [architecture policy](../policy/architecture.md), +[testing policy](../policy/testing.md), and +[documentation policy](../policy/documentation.md) throughout. Existing GoDoc, +[configuration](../config.md), [CLI](../cli.md), and +[Gitea integration](../integrations/gitea.md) documents define current contracts. +Tests supply regression evidence; assess incidental implementation assertions +against those contracts before preserving them. + +This sprint implements the Gitea backend behind a neutral boundary. Git source +publication, module/tag derivation, build targets, buildinfo, receipts, token +precedence, and optional candidate validators retain their responsibilities. +Additional backends, public backend registration, new transport retry policies, +and release publication are outside this sprint. + +## Selected design + +### Public API and configuration + +Replace the root facade's `GiteaDestination` with these facade-owned declarations: + +```go +type Forge string + +const ForgeGitea Forge = "gitea" + +type DeliveryDestination struct { + Forge Forge + BaseURL string + Owner string + Repository string + Token string +} +``` + +`DeliveryRequest.Destination` becomes `DeliveryDestination`; its other fields, +the public result shape, state values, and asset categories remain as defined +today. Use explicit conversions into internal values. The facade owns backend +selection and construction through a private helper in `delivery.go`. + +Add `delivery.forge` to the existing flat JSON delivery object. Its supported +value is `"gitea"`, corresponding to `ForgeGitea`. Omitted, empty, whitespace-only, +and unsupported selections are errors when delivery is requested. Selection is +explicit in both interfaces; neither URL inspection nor an omitted field selects +a backend. Keep `base_url`, `owner`, `repository`, `token_env`, and `token_file` +in their current locations. The owner/repository fields express the current +backend's addressing needs; future adapters can extend the neutral model when +needed without promising every forge the same address structure now. + +Keep semantic delivery validation at the delivery entry points. Strict JSON +decoding still applies to all commands, but `check`, `publish`, and `build` do +not acquire a new requirement for a delivery object, valid forge selection, or +credentials. `ci-deliver` first prepares/verifies the tagged checkout and loads +its profile as today, then checks the forge before token loading and application +dependency preparation. Ordinary `deliver` checks it before loading receipts, +building, or making hosted requests. Library selection failure returns +`DeliveryNotAttempted` with the requested version and an error before invoking +the generic workflow. No new public error type is needed. + +After explicit Gitea selection, retain `RELEASEKIT_GITEA_TOKEN` as the CLI's +existing default token environment name. Preserve configured names, environment +over file precedence, trimming and file path resolution, and status 2 for +configuration/credential errors. The library receives `Token` explicitly. +Gitea's remaining destination validation stays in the adapter. Consumer +migration replaces the public type, supplies `Forge: releasekit.ForgeGitea`, +and adds `"forge": "gitea"` to each delivery profile. Use a direct API cutover +without a legacy `GiteaDestination` alias or implicit-selection compatibility +path. + +### Internal ownership and dependencies + +| Owner | Responsibility after the sprint | +| --- | --- | +| Root facade | Public destination, explicit selection, concrete backend construction, request/result conversion. | +| `internal/forge` (new) | Backend interface, observed release/asset values, opaque handles, neutral failure classifications, and write evidence. | +| `internal/artifact` | Existing build identity helpers plus expected local assets, manifests, declaration validation, and local size/digest verification. | +| `internal/delivery` | Local source/build coordination, hosted tag comparison, page matching/creation recovery, remote content comparison, upload sequencing/recovery, observations, and final outcomes. | +| `internal/gitea` | Destination rules, HTTP operations, codecs, protocol acknowledgments, authentication and redirects, multipart streaming, and protocol-to-neutral failure conversion. | +| CLI | Profile adaptation, token loading, receipts, preparation, presentation, and exit statuses. | + +Both delivery and Gitea depend on `internal/forge`; that contract imports neither +of them. Artifact mechanics stay independent of Gitea and delivery. The generic +workflow receives a configured backend rather than destination credentials. +The facade can import Gitea to assemble the operation. The runtime facade keeps +its existing small dependency graph. + +Use one internal `Backend` interface with these logical operations. The names +below define the intended seam; small private naming adjustments are routine +implementation choices. + +| Operation | Inputs and outputs | +| --- | --- | +| `TagCommit` | Context and full tag; returns observed commit or a classified error. | +| `FindRelease` | Context and full tag; returns logical release data or a classified not-found/error. | +| `CreateRelease` | Context and requested tag, commit, title, body, draft/prerelease values; returns observed release, write evidence, and error. | +| `ListAssets` | Context and opaque release handle; returns logical remote assets or error. | +| `OpenAsset` | Context and opaque asset handle; returns `io.ReadCloser` or error. | +| `UploadAsset` | Context, opaque release handle, filename, and `io.Reader`; returns write evidence and error. | + +Logical release data contains the URL, full tag, title, body, draft/prerelease +flags, and an opaque handle. Remote asset data contains filename, reported size, +and an opaque handle. Use internal `any` handle fields with private concrete +Gitea handle values. Only the creating adapter interprets them. Keep numeric IDs +and download URLs in those private values; the shared workflow performs no +integer-ID checks or URL construction. + +Write evidence carries an `Attempted` flag and a disposition: not attempted, +accepted, rejected, conflict, or uncertain. Accepted means the adapter received +and decoded the required protocol acknowledgment; shared release-content checks +still apply. A known rejection differs from an uncertain outcome. Carry this +evidence even with an error, so successful reconciliation can retain earlier +write history. Record an attempted write when it is handed to the transport, +consistent with the existing possibly-successful-request handling; this is not +a claim that the server received it. + +Neutral failure kinds cover the existing invalid, not-found, unauthorized, +conflict, malformed, unverifiable, transport, rejected, and unknown distinctions. +Retain underlying causes through wrapping/`Unwrap` or `errors.Join`. Gitea HTTP +status and operation details belong to the adapter's diagnostic cause. Shared +recovery uses the neutral kind and write evidence, with no HTTP status branches +or Gitea error assertions. Credentials stay out of errors and results. + +The shared caller closes streams returned by `OpenAsset`, including on read +failure. For uploads it opens/closes the local file or supplies an inline +manifest reader; the adapter borrows that reader for the call and finishes its +multipart writer/pipe work before returning. Retain cancellation propagation +and cleanup rather than introducing detached recovery contexts, timeouts, or +buffering of complete binaries. + +## Stage 1: Establish the internal backend contract + +Status: pending. + +1. Add `internal/forge` with the interface and logical values specified above. + Define neutral failure and write-evidence types with internal comments about + not-found, accepted acknowledgments, uncertain writes, opaque handles, and + stream ownership. Use ordinary Go values and error wrapping. +2. Keep this package concerned with the delivery seam; production backend + selection belongs to the facade. Avoid a registry, capability negotiation, + public interfaces, or additional forge implementations in this work. +3. Identify the existing Gitea policy types replaced by later stages: + `Request`, `Result`, `ExpectedAsset`, `Inspection`, asset identities/states, + upload results, and failure assertions in `internal/delivery`. Assign their + replacement ownership as above rather than reproducing the whole Gitea + model under new names. + +Result: an internal contract implemented independently of the public facade and +delivery policy. Type-check the new package when available. Test nontrivial +error behavior where useful; recovery is tested through the shared workflow. + +## Stage 2: Move local asset and manifest mechanics + +Status: pending. + +1. Move `ExpectedAsset` and the generic work in `BuildManifest`, + `validateExpectedAssets`, and local verification from `internal/gitea` to + `internal/artifact`. Keep the filename, SHA-256, size, path, and inline-content + representation and existing accepted inputs. +2. Retain `artifact.ManifestFilename`, sorted complete manifest generation, its + byte format, duplicate/name/digest validation, and the empty-set result that + produces no manifest. +3. Adapt `internal/delivery/assets.go` and build validation to shared types. + Keep target/name correspondence, captured module/tag/commit binding, + `SourceVerified`, regular-file checks, and prebuilt output ownership. Keep + build results and the private receipt format unchanged. +4. Move/consolidate manifest and local-content tests with these responsibilities. + Preserve rejection of same-size local tampering. + +Result: local assets are declared and verified independently of Gitea. Retain +one full local size/digest preflight before hosted requests and reuse it for +uploads; subsequent transport reads stream content without a second full +preflight. This preserves existing efficiency/integrity choices rather than +adding a guarantee against concurrent file replacement. + +## Stage 3: Extract Gitea read operations + +Status: pending. + +1. Adapt the Gitea client into a backend configured with its private destination + and optional HTTP client. Implement `TagCommit`, `FindRelease`, `ListAssets`, + and `OpenAsset` using the existing HTTP helpers and routes. +2. Keep Gitea address/token rules, full-tag escaping, JSON handling, attachment + metadata validation, and HTTP status interpretation here. Map a release + lookup's 404 to neutral not-found; a missing hosted tag remains a failure to + verify source. Preserve operation-specific checks rather than applying + create-response requirements to every read. +3. Split `downloadHash` so the adapter opens the download stream and generic + delivery hashes it. Keep service-origin token forwarding, cross-origin + downloads without that token, redirect traversal, and response cleanup with + Gitea. Preserve current listing behavior; extracting the boundary does not + introduce pagination or a new response-completeness guarantee. +4. Retain local HTTP tests for routes, authentication, escaped tags, metadata + decoding, malformed lists, redirects, and unreadable assets. Adapt tests to + the read operations; shared comparison tests move in Stage 6. + +Result: backend reads expose observations and streams. Content equality, tag +comparison, page acceptance, and delivery outcomes belong to shared policy. + +## Stage 4: Extract Gitea write operations and evidence + +Status: pending. + +1. Implement `CreateRelease` from the current release POST and `UploadAsset` + from multipart upload. Keep explicit target commit, stable flags, full note, + query encoding, attachment field, and token header behavior. Protocol codecs + and private IDs remain here. +2. Populate attempted-write evidence on each path. Translate the existing + `shouldReconcileWrite` triggers into neutral dispositions: HTTP conflict is + conflict; server errors, transport interruption, cancellation/deadline, and + malformed acknowledgments are uncertain; definitive other rejections remain + rejected. Preserve causes and HTTP details. Local failures before transport + are not attempted. Shared policy decides how to recover. +3. Validate Gitea acknowledgments here, including positive IDs and the uploaded + asset's selected filename. Leave requested release tag/title/body/stability + comparison to Stage 5. A decoded acknowledgment that fails shared comparison + can still require reconciliation. +4. Accept a borrowed content reader for upload. Move file/inline-reader selection + to shared code and preserve multipart streaming, pipe closure, writer + completion, response cleanup, and context cancellation without an extra + content pass. +5. Keep HTTP tests for request bodies, uploads, malformed acknowledgments, known + rejections, ambiguous writes, and cancellation at this boundary. Recovery + orchestration tests move with policy in Stages 5 and 6. + +Result: individual writes return evidence. Follow-up lookups, content comparison, +and continuation decisions belong to shared delivery. Remove the adapter's old +orchestration as its callers move rather than retaining a parallel policy path. + +## Stage 5: Move hosted page policy and recovery + +Status: pending. + +1. Add shared page coordination in `internal/delivery` using `forge.Backend`. + After existing local source/build preflight, obtain the hosted full tag's + commit and compare it with the captured commit before page writes. Find the + release, accepting the same full tag/body/stable non-draft content and + retaining an existing title. Only a missing release triggers create. +2. Supply the full note and H1 title to `CreateRelease`. Apply shared comparison + to a successful create response, including its requested title. Reconcile + an inconsistent response as the existing client does. +3. Move create recovery out of `EnsureRelease`. Preserve the current single + follow-up lookup for a conflict or uncertain create: + + | Follow-up observation | Result | + | --- | --- | + | Matching existing page | Accept; retain the earlier write attempt. | + | Different page | Conflict. | + | Missing after conflict | Conflict. | + | Missing after uncertain write | Unknown. | + | Result cannot be established | Unknown; retain write and inspection causes. | + +4. Preserve known/read-only failures as distinct from unknown writes. Retain + resolved source identity, confirmed URL, and write history. A later asset + inspection failure after confirmed page-write progress reports partial + progress. Page-only delivery succeeds without listing/uploading assets or + creating a manifest. +5. Move page policy cases into shared tests using a small controlled backend: + matching content, conflicts, missing/wrong tag, ambiguous create recovery, + and recovered-create history. Keep wire assertions in adapter tests. + +Result: page behavior runs through the seam. Current Gitea recovery remains +unchanged; this does not promise a fixed HTTP request count for future logical +backend methods. + +## Stage 6: Move remote asset comparison and upload recovery + +Status: pending. + +1. Move inspections, asset states/identities, upload aggregation, ordering, + and recovery into `internal/delivery`. Use `ListAssets` and `OpenAsset` for + streamed SHA-256 comparison. Retain filename grouping, duplicate-name + conflicts, digest comparison, unrelated asset preservation, and unverifiable + content classification. Reported size alone remains insufficient for equality. +2. Retain complete-manifest comparison, acceptance of a matching manifest when + binaries are missing, and rejection of a subset/different manifest. Add no + independent remote-size acceptance rule. +3. Preflight declarations before uploads. Matching assets stay matching; + conflicts stop with conflict; unverifiable reads stop with not-attempted + asset work, upgraded to partial on confirmed page-write progress. Whole-list + read failures retain existing uncertain asset observations without becoming + unknown-write outcomes. +4. Upload only missing assets in filename order with `SHA256SUMS` last. Open + content through the artifact representation and close it after the borrowed + upload call. Reuse Stage 2 verification. Acknowledged uploads move from pending + to uploaded. +5. On conflict or uncertain upload, perform the existing single list/content + reconciliation for that asset. Matching bytes become matching; differing + bytes become conflicting; missing after conflict remains conflict; missing + after uncertain write or an unreadable result becomes unknown/uncertain. + Stop on unresolved/conflicting outcomes and retain later known-missing assets + as pending. Keep reconciliation free of repeated POSTs. +6. Preserve disjoint current observations, earlier successful assets, and public + states. Definitive upload failure retains partial progress; early failures + can leave declarations unreported. A new invocation re-inspects remote state + and resumes only missing work. +7. Move comparison, manifest, partial retry, and category tests here. Use a small + backend double for controlled failures, with representative assembled HTTP + coverage instead of duplicating every policy case at both layers. + +Result: shared code owns asset policy/recovery. Remove the former Gitea +`InspectAssets`, `UploadMissing`, and `UploadVerifiedMissing` entry points when +their callers/tests move; keep transport helpers needed by the backend. These +internal entry points have no public compatibility obligation. + +## Stage 7: Connect the neutral facade and CLI + +Status: pending. + +1. Implement the public declarations/conversions in Selected design. Replace + current `GiteaDestination` uses. The private facade factory switches on the + explicit forge and constructs Gitea; invalid selection returns the described + early result. +2. Change the internal delivery request to receive the backend. Replace Gitea + asset/error references in request/result conversion and build validation. + Pass delivery's single captured module/source identity through its existing + workflow. Retain local source rechecks, temporary output cleanup, and + prebuilt output ownership. +3. Add `Forge` to CLI delivery settings with JSON key `forge`. Validate selection + during delivery adaptation before credentials or subsequent delivery work, + returning status 2 for invalid selection. Map the facade's supported value; + preserve strict decoding and non-delivery behavior. +4. Update `deliveryDestination`, `deliverWithDestination`, ordinary delivery, + and CI delivery. Retain tagged profile loading, module/tag checks, dependency + preparation, receipt binding, and output/exit classification. Keep token + resolution in the CLI with current precedence and Gitea environment default. +5. Add `"forge": "gitea"` to ReleaseKit's profile, maintained delivery profiles, + examples, and active test fixtures. Check/build-only profiles can keep + omitting delivery. Keep token names, file paths, and Woodpecker secret mappings. + +Result: both interfaces select Gitea explicitly through a neutral destination +and share generic orchestration. CI preparation retains its earlier position +because it establishes which tagged profile is authoritative. + +## Stage 8: Complete regression coverage and dependency checks + +Status: pending. + +1. Finish adapting facade, CLI, HTTP fixture, and benchmark callers. Retain + root/nested/major-module identities, page-only delivery, managed/prebuilt + binaries, tagged retries, source checks, partial/conflict/unknown reporting, + and cleanup coverage. +2. Add focused API/CLI selection cases: explicit Gitea succeeds; omitted, blank, + and unsupported values fail; the URL does not infer a backend. For CLI errors, + assert status/semantic diagnostics and that token access, dependency + preparation, and hosted work were not reached. Reuse existing non-delivery + tests to show those commands still need neither selection nor credentials. +3. Use controlled backend tests to demonstrate shared page/asset policy without + Gitea imports. Consolidate obsolete client-policy tests rather than retaining + wrappers. Keep representative real-Gitea HTTP assembly tests for page + creation and binary delivery/retry, exercising conversions and opaque handles. +4. Extend `internal/apicheck` with a focused check that shared delivery, forge + contracts, and artifact mechanics have no production dependency path to + `internal/gitea`. Preserve export ownership/runtime checks, keeping new + delivery-related packages out of the runtime dependency graph through the + existing checking mechanism. Add a small violation fixture following current + apicheck tests. +5. Review stream/error paths for cancellation identity, reader closure, + multipart worker completion, and causes. Reuse cancellation tests; add a + narrow regression for a gap introduced by moved ownership. Keep the default + suite offline and credential-free. + +Result: selected requirements have meaningful coverage and clear test owners. +Run relevant package tests once assembled; Stage 9 owns the final full checks. + +## Stage 9: Document the cutover and verify the sprint + +Status: pending. + +1. Add the next sequential ADR (currently `0006`) using the documentation + policy's lightweight Nygard format. Record the shared-policy/backend boundary, + neutral facade-owned destination, explicit selection, alternatives, and + consequences. Keep progress in these roadmaps. +2. Update GoDoc, `docs/config.md`, `docs/cli.md`, and + `docs/consumers/delivery.md` for the neutral destination and selection. + Update current README/runbook/example snippets where affected. Keep Gitea + wire/authentication details in `docs/integrations/gitea.md`; describe shared + coordination in internal documentation linked from `docs/internal/overview.md`. + Align architecture policy wording with the implemented boundary while + preserving its broader constraints. +3. Write `docs/releases/v0.6.0.md`, the next minor release note, explaining the + features, compatibility impact, and public type/profile migration. Follow + the [release policy](../policy/release.md) and + [runbook](../release.md) for note/footer requirements. Explain the unchanged + Gitea token default and profile updates before tagging: CI reads the tagged profile. + Preserve historical notes. Prepare documentation here; tagging, pushing, + and publication remain separate release work. +4. Check maintained JSON/Go examples, profile-generating fixtures, and Woodpecker + tests for explicit selection. Search active code/docs for obsolete public + type uses and implicit-selection instructions, distinguishing history and + migration examples. Verify documentation links, fragments, and whitespace. +5. Run `go test ./...` and `go run ./internal/apicheck` from the repository root + with prepared local dependencies and offline module/toolchain access. These + include representative CLI/consumer workflows. Reuse successful checks for + unchanged content; fix failures and rerun affected checks. No live forge, + real token, CI run, or consumer pilot is required for sprint completion. +6. Review the final diff and dependency flow against every feature target and + stage. Verify generic policy has no Gitea dependency; only the adapter + interprets its handles/statuses; the assembled workflow retains recovery + outcomes; current delivery profiles explicitly select Gitea; migration + instructions match the API/configuration; local preflight is reused; and + content comparison and transport remain streamed. +7. Mark stages complete as verified. Commit completed work under the agent + policy, report validation and the final commit, and leave a clean workspace. + Retain accurate completed plans for later release-preparation cleanup. + +Result: the initial refactor, migration guidance, and offline verification are +complete. All stages proceed to this end state within the sprint.