# API and interface contract audit ## Status and scope Audit completed against source commit `3f00ad2` on 2026-11-01. The findings below are resolved as recorded in their completion markers. The audit itself changed documentation only. The review covered exported declarations and conversions in both public facades; candidate capture, validation, callbacks, and publication outcomes; module selection and dependency preparation; build results and CLI receipts; shared artifact and delivery contracts; Gitea observations and write evidence; and subprocess, blob-reader, and output ownership. GoDoc, current consumer and integration documentation, accepted architecture decisions, and existing tests provided the contract evidence. The review used the code graph for discovery and inspected implementation and tests directly, including the build package excluded by the graph index. ## Current architecture - The root `releasekit` facade owns consumer request/result types, conversions, backend selection, and public validation and preflight errors. It exposes checking, publication, builds, preparation, and hosted delivery. - The separate `buildinfo` facade exposes runtime version access through a small internal implementation. The builder and runtime implementation share the version-injection contract. - Candidate and validation packages own captured-source checks and callback sequencing. Publication adds authoritative Git endpoint guards, annotated tag creation, exact-object pushes, and reconciliation. - Release identity resolves module selection and derives module paths, tags, and note locations. Build and artifact packages own compilation, source observations, output declarations, digests, and manifests. - Shared delivery owns page matching, asset inspection, additive uploads, and recovery outcomes. It consumes `internal/forge.Backend`; the Gitea adapter owns HTTP operations, opaque handles, and protocol failure conversion. - The CLI owns profiles, credential loading, local build receipts, CI preparation, presentation, and exit statuses. These boundaries are consistent with the [architecture policy](../policy/architecture.md) and [ADR 0006](../adr/0006-neutral-forge-delivery-and-preflight.md). The findings concern behavior and descriptions within these boundaries; they do not require an architectural redesign. ## Findings ### 1. Build source inspection loses cancellation identity **Priority:** medium. **Status:** complete. **Completion evidence:** `28a4144`: synchronized cancellation, deadline, partial-artifact, and race checks pass. **Relevant code:** `cleanSource`, `gitCommit`, and `checkVersion` in [internal/build/build.go](../../internal/build/build.go), reached through public `Build` and managed delivery builds; the process error contract in [internal/process/process.go](../../internal/process/process.go). `cleanSource` wraps the error returned by `exec.CommandContext(...).Output()` without checking the context. Cancellation while `git status` runs therefore returns a killed-process error rather than an error matching `context.Canceled` or `context.DeadlineExceeded`. The neighboring `gitCommit` and `checkVersion` helpers translate interrupted commands to the context error, and the shared process runner does the same. Existing `TestBuildCancellationReturnsCompletedArtifacts` establishes identifiable cancellation during compilation, but does not exercise this source-inspection path. A temporary public-API probe blocked the first build-time `git status` with a local Git wrapper, then canceled the context. `Build` returned `inspect build source: signal: killed`; `errors.Is(err, context.Canceled)` was false. No Go compilation was needed. The same helper runs after compilation, so this discrepancy also affects callers receiving completed artifacts. **Consequences:** callers cannot reliably distinguish an interrupted build from a Git/source-inspection failure using the error chain. Managed delivery inherits that discrepancy when its builder reaches this path. **Potential remediation:** preserve the context error in `cleanSource`, using its neighboring helpers or the shared process boundary as the model. A focused regression check should interrupt source inspection and verify cancellation identity while retaining any partial build facts. This does not require a new error type or cancellation policy. ### 2. A release lookup can produce an unusable asset handle without an error **Priority:** low. **Status:** complete. **Completion evidence:** `9fa4407`: backend and public page-only/prebuilt regressions pass with malformed-handle classification. **Relevant interfaces:** `forge.Backend.FindRelease` and `ListAssets` in [internal/forge/forge.go](../../internal/forge/forge.go); Gitea `FindRelease`, `ListAssets`, and `CreateRelease` in [internal/gitea/backend.go](../../internal/gitea/backend.go); `listAssets` in [internal/gitea/assets.go](../../internal/gitea/assets.go). Gitea `FindRelease` returns success and a non-nil opaque handle when the decoded release ID is missing, zero, or negative. Passing that handle to `ListAssets` then fails locally with `invalid_request` because the ID is not positive. The handle came directly from a successful backend operation; the missing identity originated in the service response. `CreateRelease` already treats an absent positive ID as a malformed acknowledgment. A local HTTP probe returned the correct tag and note body with no release ID. Page-only delivery completed. Binary delivery using the same page failed with `invalid_request: gitea list release assets: invalid_request: release ID must be positive`, without making an asset-list request. **Consequences:** binary delivery receives an unusable successful observation and attributes a service-response defect to the request. This weakens the failure distinction at the neutral backend boundary and gives callers misleading diagnostics. **Potential remediation:** classify the missing service identity when an asset operation needs it, distinguishing an incomplete backend-produced handle from an invalid handle supplied to the adapter. Alternatively, validate the ID at lookup if a deliberate decision makes it required for every successful lookup. The narrower approach preserves the existing minimal page-only lookup behavior: `TestBackendFindReleaseDoesNotRequireWriteResponseFields` explicitly accepts a response without an ID. This finding does not establish a new requirement for page URLs or write-response fields on page-only reads. ### 3. Dependency preparation uses a different repository anchor **Priority:** low; contract clarification needed. **Status:** complete. **Completion evidence:** `b47202a`, `5aba4c6`, `abbab3c`: checkout discovery, standalone preparation, and build-composition regressions pass. **Relevant API:** `DependencyPreparationRequest` and `PrepareBuildDependencies` in [preparation.go](../../preparation.go), compared with `Build`, `Check`, and `Deliver`, and `ResolveSelection` in [internal/releaseidentity/identity.go](../../internal/releaseidentity/identity.go). The preparation request calls `ModuleDir` repository-relative and says its empty value selects the repository root. Its implementation uses the supplied `RepositoryDir` directly as the selection anchor. Other orchestration requests accept a directory within a checkout and first resolve the containing repository root. The preparation request does not explain that its anchor differs or explicitly require an already resolved root. In a two-module local checkout, a public-API probe supplied the child directory as `RepositoryDir` and omitted `ModuleDir` for both operations. `Build` selected the root module. `PrepareBuildDependencies`, observed with a local Go wrapper, ran in the child module. A nonempty repository-relative selection would also be interpreted beneath that child directory. **Consequences:** consumers reusing one repository location across operations can prepare dependencies for a different module, or fail to locate the requested module. The CLI avoids this discrepancy by resolving the root before calling the preparation facade. **Potential remediation:** make the preparation anchor explicit in GoDoc and consumer guidance, or align checkout-root resolution with the other facades. Existing preparation tests also support supplied directories outside Git repositories; alignment should account for that behavior rather than silently introducing a Git-checkout prerequisite. Requiring a pre-resolved root is a viable documented alternative, with additional normalization work for API callers. The current evidence establishes the inconsistency, not a requirement to remove standalone directory preparation. ### 4. Empty-prebuilt GoDoc omits the required source identity **Priority:** low. **Status:** complete. **Completion evidence:** `d368bb3`: GoDoc and consumer guidance describe matching empty results; page-only regressions pass. **Relevant API:** `DeliveryRequest` in [delivery.go](../../delivery.go), `validateBuildIdentity` in [internal/delivery/delivery.go](../../internal/delivery/delivery.go), and the [Go API delivery guidance](../consumers/delivery.md#go-api). The request GoDoc says that an empty `Prebuilt` result performs page-only delivery. In practice, `Prebuilt: &BuildResult{}` fails before preflight with `build result does not identify the selected tagged module`. Identity checking is unconditional for non-nil prebuilt results; only the binary-source and artifact requirements depend on selected targets. The consumer guide more precisely says a *matching* empty prebuilt result. The temporary public-API probe confirmed the zero-result rejection. **Consequences:** a caller following the exported request documentation can reasonably supply a zero result as the advertised empty selection and receive an unexpected validation error. **Potential remediation:** clarify the GoDoc to say that a prebuilt result with no targets or artifacts still needs matching repository, module, tag, commit, and version identity. Recommend nil `Build` and nil `Prebuilt` for ordinary page-only delivery. This aligns documentation with the existing implementation and consumer guide without adding a zero-result compatibility path. ### 5. Prebuilt delivery guidance promises hashing before every hosted request **Priority:** low. **Status:** complete. **Completion evidence:** `d368bb3`: consumer guidance matches preflight-first order; the existing preflight-before-hashing regression passes. **Relevant documentation:** the prebuilt paragraph in the [Go API delivery guidance](../consumers/delivery.md#go-api), compared with `Deliver` GoDoc in [delivery.go](../../delivery.go) and [ADR 0006](../adr/0006-neutral-forge-delivery-and-preflight.md). The guide says current prebuilt files are checked against recorded digests "before any hosted request." The implementation and accepted preflight decision place read-only credential verification before full local artifact hashing. Other sections of the same guide correctly describe that order. **Consequences:** readers receive conflicting descriptions of when delivery can contact the service, especially when a local prebuilt file is corrupt or unreadable. They cannot use the quoted statement to predict an entirely local failure before network access. **Potential remediation:** change the statement to "before any hosted write" and retain the documented preflight-first order. Existing `TestAutomaticPreflightFailurePrecedesPrebuiltArtifactHashing` covers that selected behavior. No production ordering change is recommended. ## Validation and interpretation The following completed successfully with Go 1.26.10 on Linux/amd64 and `GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off`: - `go test ./...` - `go run ./internal/apicheck` - `go test ./internal/process ./internal/gotool ./internal/gitea ./internal/delivery ./internal/releaseidentity -count=1` Temporary probes used public APIs, disposable local Git repositories, local command wrappers, synthetic credentials, and a local HTTP server. They confirmed the observations described above without modifying production code or adding repository tests. Passing existing tests does not cover the newly identified source-inspection cancellation case or resolve the documented ambiguities. No additional actionable contract defect was established in facade type ownership and conversions, callback sequencing, validation input selection, receipt identity binding, source publication reconciliation, runtime version lookup, or stream ownership. This is an implementation and local-fixture audit; it does not establish live-forge or live-CI behavior. Successful credential preflight continues to verify the available access evidence, and `SourceVerified` continues to describe source observations rather than an attestation. The remediation addresses the cancellation error chain and asset-handle failure classification and aligns preparation and delivery descriptions with the selected behavior. The findings did not call for new public extension interfaces, compatibility shims, stricter page-only response requirements, or a new release procedure.