34 KiB
Implementation plan: neutral forge delivery and credential preflight
Status: implementation in progress; Stages 1–7 are complete and Stages 8–10 are pending.
Purpose and execution
Implement the target in the feature roadmap. That document
owns scope and user choices; this plan owns concrete design and ordered work.
The planning baseline is 44823ce; source implementation remains unchanged
from ac78e7d. Gitea delivery, token files, and forbidden_files work today.
The neutral destination and credential preflight are planned.
Execute Stage 1 through Stage 10 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 10 owns final assembled verification. Record completion here and follow the agent policy for commits and validation reporting.
Read the development guide and applicable policies. Use the architecture policy, testing policy, and documentation policy throughout. Existing GoDoc, configuration, CLI, and Gitea integration 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:
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. Credential preflight failures use the classification
described below.
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.
Credential preflight contract and ordering
Add PreflightDelivery(ctx context.Context, destination DeliveryDestination) error
to the root facade. It checks the selected destination without requiring source,
version, tag, note, or artifact inputs. Deliver automatically uses the same
internal preflight implementation; callers do not need to invoke the standalone
operation for ordinary delivery.
Use a facade-owned DeliveryPreflightError with Kind and Err fields,
Error, and Unwrap. Its named string kind, DeliveryPreflightFailureKind,
has these constants:
| Constant | Value |
|---|---|
DeliveryPreflightMissingCredential |
missing_credential |
DeliveryPreflightInvalidCredential |
invalid_credential |
DeliveryPreflightInsufficientPermissions |
insufficient_permissions |
DeliveryPreflightInaccessibleDestination |
inaccessible_destination |
DeliveryPreflightUnavailable |
unavailable |
Convert internal classifications explicitly and retain causes and cancellation identity without exposing internal types in exported signatures. The unavailable category includes network/service failures and malformed responses that prevent verification. A 404 can mean hidden or missing repository access; diagnostics must preserve that ambiguity. A 403 establishes denied access, not necessarily an expired or invalid token.
Preflight checks local destination rules and nonblank credentials, then invokes the backend's read-only authenticated repository probe. It fails on rejected credentials, inaccessible target access, and reliably established deficiencies in permissions needed for delivery. Scope checks to release/asset delivery; code-push permission alone is not an equivalent release permission. Unexposed token scopes or write permissions remain unverified and do not independently fail preflight. A nil error establishes authentication and repository access plus no known delivery-permission deficiency; it does not certify every future write. Preserve ordinary authorization and write recovery. Do not create test releases, upload test assets, or request extra user/admin token scopes.
Library delivery performs cheap request/source/note checks, then credential preflight before managed builds or full artifact size/digest verification. Ordinary CLI delivery resolves credentials as today and relies on this automatic library preflight. Reading a prebuilt receipt can remain a cheap earlier step. After preflight succeeds, full local content verification still precedes every hosted release/asset write. This deliberately permits the earlier read-only credential probe before local artifact verification.
For binary ci-deliver, load the verified tagged profile and resolve credentials,
invoke the standalone facade preflight, then prepare application dependencies.
Subsequent Deliver performs its normal preflight before building; that later
check is fresh after network-capable dependency preparation. Page-only CI has
no application dependency preparation and can rely on Deliver alone. No
long-lived validation cache or public preflight-bypass setting is needed.
Automatic preflight failure leaves delivery not_attempted, including service
unavailability; unknown-write outcomes remain reserved for unresolved writes.
CLI missing/invalid credentials, established insufficient permissions, and
inaccessible destinations return status 2. Unavailable verification and
cancellation return status 1 with their actual causes. Existing local profile
errors keep status 2. Document these earlier failures and classifications as
part of the next minor release's operational compatibility impact.
check and standalone build acquire no credentials or online preflight.
publish retains Git's native SSH/HTTPS authentication and remote guards; it
does not invoke hosted delivery preflight or require a forge token. Consumers
can explicitly call PreflightDelivery before their own preparation or source
publication without making that a new mandatory publication gate. Application
dependency credentials stay with the existing Go/Git preparation mechanisms.
Internal ownership and dependencies
| Owner | Responsibility after the sprint |
|---|---|
| Root facade | Public destination and callable preflight, explicit selection, concrete backend construction, request/result/error conversion. |
internal/forge (new) |
Backend interface, preflight classifications, observed release/asset values, opaque handles, neutral failures, and write evidence. |
internal/artifact |
Existing build identity helpers plus expected local assets, manifests, declaration validation, and local size/digest verification. |
internal/delivery |
Automatic preflight coordination, 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, authenticated preflight probes and permission evidence, 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 |
|---|---|
Preflight |
Context; checks the backend's configured credentials/repository through read-only requests and returns a classified preflight error or nil. |
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: complete.
- Add
internal/forgewith the interface and logical values specified above. Define preflight classifications, neutral failures, and write-evidence types with comments about not-found, accepted acknowledgments, uncertain writes, opaque handles, and stream ownership. Use ordinary Go values and error wrapping. - 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.
- Identify the existing Gitea policy types replaced by later stages:
Request,Result,ExpectedAsset,Inspection, asset identities/states, upload results, and failure assertions ininternal/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: complete.
- Move
ExpectedAssetand the generic work inBuildManifest,validateExpectedAssets, and local verification frominternal/giteatointernal/artifact. Keep the filename, SHA-256, size, path, and inline-content representation and existing accepted inputs. - Retain
artifact.ManifestFilename, sorted complete manifest generation, its byte format, duplicate/name/digest validation, and the empty-set result that produces no manifest. - Adapt
internal/delivery/assets.goand 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. - 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 writes 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: complete.
- Adapt the Gitea client into a backend configured with its private destination
and optional HTTP client. Implement
TagCommit,FindRelease,ListAssets, andOpenAssetusing the existing HTTP helpers and routes. - 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.
- Split
downloadHashso 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. - 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 7.
Result: backend reads expose observations and streams. Content equality, tag comparison, page acceptance, and delivery outcomes belong to shared policy.
Stage 4: Implement read-only Gitea credential preflight
Status: complete.
- Implement backend
Preflightwith local destination/token validation and authenticated repository-scoped reads. Perform no source/tag checks here so the same probe works before a release tag exists. - Resolve the configured repository with
GET /repos/{owner}/{repo}, then use its ID with the authenticatedGET /repositories/{id}route. Validate the required repository identity and successful response shape. The second route requires authentication and repository scope, so public anonymous repository access alone cannot satisfy preflight. Keep headers, IDs, and route details within Gitea. See the upstream route definitions. - Interpret reliable release/asset permission evidence when available. Gitea's
basic
permissions.pushdescribes code pushes, and a successful repository GET does not establishwrite:repositorytoken scope. Treat unexposed release-write/token-scope evidence as unverified. Do not equate absent metadata with denial or add/userprobes requiring unrelated user scope. The upstream repository model and scope documentation describe those distinctions. Keep later write authorization authoritative. - Classify missing/blank credentials locally, server-rejected authentication, permission denial, inaccessible/hidden destinations, and unavailable or malformed verification responses. Preserve cancellation and useful HTTP causes without including the token. Unsupported/malformed destination settings retain actionable local configuration errors.
- Use a local HTTP fixture to cover accepted repository-scoped credentials, invalid tokens, denied/missing repositories, service/decoding failure, and cancellation. Demonstrate that anonymous public access cannot pass the authenticated step and that unexposed write scopes are not claimed as verified or treated as a new failure. Assert read-only behavior at this seam.
Result: the backend checks authentication and repository access before costly delivery work and accurately represents the limits of available permission evidence. It uses no extra account/admin credentials or destructive probe.
Stage 5: Extract Gitea write operations and evidence
Status: complete.
- Implement
CreateReleasefrom the current release POST andUploadAssetfrom 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. - Populate attempted-write evidence on each path. Translate the existing
shouldReconcileWritetriggers 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. - Validate Gitea acknowledgments here, including positive IDs and the uploaded asset's selected filename. Leave requested release tag/title/body/stability comparison to Stage 6. A decoded acknowledgment that fails shared comparison can still require reconciliation.
- 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.
- 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 6 and 7.
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 6: Move hosted page policy and recovery
Status: complete.
-
Add shared page coordination in
internal/deliveryusingforge.Backend. After credential preflight and local source/build verification, 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. -
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. -
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. -
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.
-
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 7: Move remote asset comparison and upload recovery
Status: complete.
- Move inspections, asset states/identities, upload aggregation, ordering,
and recovery into
internal/delivery. UseListAssetsandOpenAssetfor 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. - 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.
- 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.
- Upload only missing assets in filename order with
SHA256SUMSlast. 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. - 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.
- 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.
- 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 8: Connect the neutral facade and CLI
Status: pending.
- Implement the public declarations/conversions in Selected design. Replace
current
GiteaDestinationuses. The private facade factory switches on the explicit forge and constructs Gitea; invalid selection returns the described early result. - Implement
PreflightDeliveryandDeliveryPreflightErrorconversions using that same backend factory and preflight implementation. Support destination checking without a repository checkout or published tag. Preserve causes withUnwrapand keep all exported named types facade-owned. - 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. Invoke backend preflight after cheap local checks
and before managed builds/full artifact hashing. Convert its failures to
the facade error and retain
not_attempteddelivery state. - Add
Forgeto CLI delivery settings with JSON keyforge. 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. - 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. For binary CI, invokePreflightDeliveryafter tagged profile/credential loading and before dependency preparation. Ordinary and page-only delivery rely on automatic library preflight; subsequent binary CI delivery rechecks after dependency preparation. Apply the preflight exit classifications above. - 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 and automatic credential preflight. CI checkout preparation retains its earlier position because it establishes which tagged profile is authoritative; credential verification precedes application downloads.
Stage 9: Complete regression coverage and dependency checks
Status: pending.
- 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.
- 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.
- Cover automatic and standalone preflight with controlled failures: library
and ordinary CLI failures precede builds/full artifact hashing and writes;
binary CI failures precede dependency downloads; page-only CI still checks
credentials; standalone checks need no checkout or tag. Assert typed error
kinds, causes, CLI status, and
not_attemptedeven on unavailable service. Verify unavailable permission metadata alone is not failure and no preflight path performs test writes. Update affected "before hosted requests" fixtures to permit credential reads while retaining assertions that local verification precedes writes. Keepcheck,build, and sourcepublishindependent of hosted credential preflight and preserve the existing native Git guards. - 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.
- Extend
internal/apicheckwith a focused check that shared delivery, forge contracts, and artifact mechanics have no production dependency path tointernal/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. - 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 10 owns the final full checks.
Stage 10: Document the cutover and verify the sprint
Status: pending.
- 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, credential preflight ownership and evidence limits, alternatives, and consequences. Keep progress in these roadmaps. - Update GoDoc,
docs/config.md,docs/cli.md, anddocs/consumers/delivery.mdfor the neutral destination, selection, automatic preflight, callable preflight, typed failures, and CLI exit classifications. Update current README/runbook/example snippets where affected. Keep Gitea wire/authentication details indocs/integrations/gitea.md; describe shared coordination in internal documentation linked fromdocs/internal/overview.md. Align architecture policy wording with the implemented boundary while preserving its broader constraints. Document the authenticated repository probe and unverified write scopes in the Gitea guide. Describe standalone preflight as a caller option; preserve existing source-publication completion and credential requirements. - 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 and runbook for note/footer requirements. Explain the unchanged Gitea token default and profile updates before tagging: CI reads the tagged profile. Include fail-fast credential checks, additional read-only requests, failure classifications, and the limited meaning of successful preflight. Explain that local artifact verification now follows credential reads and still precedes hosted writes. Preserve historical notes. Prepare documentation here; tagging, pushing, and publication remain separate release work. - 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.
- Run
go test ./...andgo run ./internal/apicheckfrom 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. - 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; credential failures stop before costly delivery work; source publication remains independent; local content verification is reused before writes; and comparison/transport remain streamed.
- 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.