Prepare v0.7.0 notes and retire completed roadmaps

This commit is contained in:
2026-11-02 13:54:16 +00:00
parent e736166a39
commit 8d4895f4d5
16 changed files with 173 additions and 2413 deletions

106
docs/releases/v0.7.0.md Normal file
View File

@@ -0,0 +1,106 @@
# ReleaseKit v0.7.0
## Summary
ReleaseKit improves source-publication integrity, cancellation, credential-safe
diagnostics, partial-result reporting, and temporary-file cleanup. It also bounds
retained Go failure diagnostics and remote attachment comparison work while
preserving the existing public facades and configuration formats.
## Publication and Git operations
- Exact tag pushes disable submodule recursion, correcting inherited
`push.recurseSubmodules=only` settings that could report success without
publishing the release tag.
- ReleaseKit-owned Git inspection and preparation use original objects when
replacement refs exist. Committed module identity, release notes, validation
inputs, and build source eligibility use that consistent view. Ordinary dirty
builds remain supported, with their source eligibility reported separately.
- A verified local tag object remains available in `PublicationResult.TagObject`
when later inspection fails. Errors identify a possibly retained local ref
when its object ID could not be established. Failed verification stops before
a push.
- HTTP(S) userinfo is masked in owned Git diagnostics and CLI output, including
literal apostrophes in credentials and failed CI checkout preparation. Native
Git authentication, exact endpoint comparisons, and the raw captured
`PublicationResult.Endpoint` value retain their existing behavior.
- Child environment overlays preserve the working-directory-derived `PWD` on
POSIX systems. Native Git hooks now receive the expected directory environment
when ReleaseKit is invoked from another checkout or module subdirectory.
## Cancellation and diagnostic retention
- Buffered subprocess output has bounded I/O teardown after cancellation or
child exit, including when descendants retain inherited output descriptors.
Piped Git-reader cancellation closes owned pipes and joins child cleanup.
Cancellation remains recognizable through `errors.Is`; ordinary execution
failures preserve their underlying causes.
- A queued blob read rechecks cancellation after acquiring the reader mutex,
preserving a healthy batch session for later reads.
- Status-only Go commands and dependency preparation retain a 1 MiB diagnostic
tail. Failed commands identify omitted earlier output. Existing streaming
output continues to be forwarded in full, and commands requiring returned
output keep their complete capture.
## Builds and hosted delivery
- Completed build artifacts are reported only after hashing succeeds. A hashing
failure identifies the affected output and preserves earlier completed
artifacts and their digests.
- Temporary allocation and cleanup failures retain their paths and underlying
causes, including candidate builds, native version checks, delivery builds,
tag-message files, and build receipts.
- A managed-build cleanup failure after confirmed hosted delivery returns an
error alongside `DeliveryComplete` and the confirmed asset observations. The
CLI reports completed remote work and the local cleanup failure, returning
operational status 1.
- Gitea attachment downloads follow at most ten redirects, retaining cycle
detection and per-origin authorization handling. Limit exhaustion is
unverifiable content.
- Remote attachment comparison reads at most the locally verified expected size
plus one byte. Shorter or longer content conflicts; equal-length content still
requires the expected digest. Read, close, and cancellation errors remain
inspection failures, with existing attempted-write reconciliation preserved.
Shared Git observations, validation sequencing, artifact declaration matching,
and CLI workflows were consolidated internally. Consumer-facing types continue
to belong to the root `releasekit` and runtime `buildinfo` facades.
## Compatibility and upgrade guidance
The module identifier remains `go.maximumdirect.net/releasekit`, and the Go
consumer minimum remains 1.26.0. Existing v0.6.0 API calls, profile fields, and
build receipt formats remain supported. This minor release changes operational
behavior through the diagnostic and redirect budgets and cleanup-error reporting
described above.
When adopting v0.7.0:
1. Update library and CLI dependencies to v0.7.0 using the existing import paths
and profiles.
2. Handle a delivery error together with `DeliveryResult.State`. For
`DeliveryComplete`, address the reported local cleanup path; the confirmed
hosted delivery is already complete. Continue to resolve partial, conflicting,
and unknown outcomes through their existing recovery guidance.
3. Retain candidate validation's streamed `Output` when complete test diagnostics
are needed. Status-only failure text now contains the retained tail and an
omission notice when applicable.
4. When using Git replacement refs, expect release evidence to be evaluated
against original committed objects. A build of dirty or replacement-derived
working content can succeed while remaining ineligible for binary delivery.
5. Inspect the reported local tag ref after a tag-creation or verification
failure, including when `TagObject` is empty. Continue to treat the raw
`PublicationResult.Endpoint` as sensitive when handling it in application
output or logs.
See the [publication guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/consumers/publish.md),
[build guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/consumers/build.md),
[delivery guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/consumers/delivery.md),
[CLI contract](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/cli.md),
and [Gitea integration guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/integrations/gitea.md)
for current behavior. Source publication and hosted delivery retain their
separate outcomes under the [maintainer procedure](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/release.md).
[COPYRIGHT](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/COPYRIGHT) |
[LICENSE](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/LICENSE) |
[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/oss/attribution.md)

View File

@@ -1,407 +0,0 @@
# Audit remediation target
The target state and policy choices below are accepted. They cover the 27
findings consolidated from the eleven reports in [`audit/`](audit/) into 20
work items. Original reports retain reproduction evidence and limitations.
The implementation baseline is `b5b3f09`; only documentation changed since the
audits' source baselines. Implementation status and ordered work are owned by
[the implementation plan](implementation.md).
## Accepted scope and priorities
P1 addresses publication integrity, credential confidentiality, and cancellation.
P2 covers recovery, completed-output contracts, cleanup, and asset inspection.
P3 covers focused internal improvements. P4 retains the accepted deferral of
production changes while response sizing and exclusion performance are assessed.
The target preserves the root and `buildinfo` facades, explicit forge selection,
caller ownership of prebuilt artifacts, credential origin checks, and additive
hosted delivery. The accepted resource policies are ten followed redirects per
asset request and a 1 MiB retained tail for status-only Go diagnostics. A11 and
A17 assessment supplies evidence for later policy or optimization decisions.
| ID | Priority | Target outcome |
| --- | --- | --- |
| A01 | P1 | Exact source-tag publication with submodule recursion disabled. |
| A02 | P1 | Consistent original-object release evidence. |
| A03 | P1 | Bounded command I/O shutdown and joined pipe ownership. |
| A04 | P1 | Credential-safe Git URL diagnostics. |
| A05 | P2 | Accurate retained-tag recovery evidence. |
| A06 | P2 | Digests on every completed artifact. |
| A07 | P2 | Actionable cleanup errors with accurate primary outcomes. |
| A08 | P2 | At most ten followed redirects per asset request. |
| A09 | P2 | Content comparison bounded by expected size plus one byte. |
| A10 | P3 | Bounded status diagnostics and complete existing forwarding. |
| A11 | P4 | Response-size assessment; production caps deferred. |
| A12 | P3 | Shared Git observations with caller-owned policy. |
| A13 | P3 | Clear Go-validation and committed-tree ownership. |
| A14 | P3 | One internal normalized build-settings representation. |
| A15 | P3 | Private command workflows behind a small dispatcher. |
| A16 | P3 | Shared pure artifact-declaration matching. |
| A17 | P4 | Exclusion benchmarks; production indexing deferred. |
| A18 | P3 | Queued cancellation preserves a healthy reader session. |
| A19 | P3 | Tests protect behavior and selected resource concerns. |
| A20 | P3 | Unused helpers removed with live coverage preserved. |
## Accepted outcomes and rationale
### A01. Inherited submodule push mode can suppress publication
Sources: [correctness §1](audit/01-correctness.md#1-high-publication-can-report-success-while-leaving-the-tag-unpublished)
and [testing §1](audit/08-testing.md#1-publication-fixtures-omit-git-settings-that-change-what-is-published).
**Target:** Source publication pushes only the selected annotated tag object,
with submodule recursion disabled. A successful publication identifies the
exact tag published to the authoritative endpoint.
**Rationale:** An inherited submodule-only push mode can otherwise return
success while leaving the release tag absent.
**Considered alternative:** Successful-push read-back supplies independent
evidence but adds network work and an uncertain outcome when that read fails.
The selected remedy controls the owned push operation.
### A02. Replacement objects separate validated and published content
Sources: [correctness §2](audit/01-correctness.md#2-high-git-replacement-objects-can-separate-validated-content-from-published-source)
and [testing §1](audit/08-testing.md#1-publication-fixtures-omit-git-settings-that-change-what-is-published).
**Target:** ReleaseKit-owned Git inspection uses original objects consistently
for module identity, notes, candidate files, and source eligibility. Working
source is compared with that view before release evidence is accepted. Ordinary
dirty builds retain their supported behavior; unrelated replacement refs remain
permitted.
**Rationale:** Replacement interpretation can make validated bytes differ from
the original objects transferred to the remote, despite matching recorded IDs.
**Considered alternative:** Rejecting replacements affecting selected objects
is viable but needs additional commit/tree/blob reachability analysis. The
accepted original-object view provides one consistent interpretation.
### A03. Inherited descriptors defeat cancellation and bounded recovery
Sources: [correctness §3](audit/01-correctness.md#3-medium-inherited-process-pipes-can-defeat-cancellation-and-recovery-deadlines),
[concurrency §1](audit/04-concurrency.md#1-medium-inherited-descriptors-can-keep-canceled-operations-and-reader-shutdown-blocked),
and [testing §2](audit/08-testing.md#2-cancellation-fixtures-do-not-cover-inherited-subprocess-descriptors).
**Target:** Buffered command execution has bounded I/O teardown after
cancellation or child exit. Piped exchanges close owned pipes on operation
cancellation and join their cancellation work and child wait. Context error
identity and the existing bounded publication reconciliation remain meaningful.
**Rationale:** Descendants can retain descriptors after the immediate child
exits or is canceled. Both buffered execution and batch-reader cleanup need
ownership of their shutdown paths.
**Considered alternative:** Process-group termination can stop descendants but
introduces platform and ownership policy beyond completing the selected
cancellation behavior. Standard subprocess and pipe mechanisms are the accepted
scope.
### A04. Git URL credentials appear in errors and CLI output
Source: [security §1](audit/07-security.md#1-medium-publication-diagnostics-expose-credentials-embedded-in-git-urls).
**Target:** ReleaseKit-owned Git URL diagnostics and CLI output mask HTTP(S)
userinfo credentials, including username-carried tokens. Transport and exact
comparisons use original URLs. `PublicationResult.Endpoint` retains its raw
captured value, with its sensitivity documented.
**Rationale:** The observed mismatch and failure paths can expose configured
credentials to readers of logs or shared diagnostics.
**Considered alternative:** Sanitizing the public endpoint value reduces
accidental logging but changes the captured-URL contract. The accepted choice
sanitizes diagnostic boundaries while preserving that contract.
### A05. Post-creation tag inspection loses recovery evidence
Sources: [error handling](audit/06-error-handling.md#a-post-creation-inspection-failure-hides-a-retained-local-tag)
and [testing §3](audit/08-testing.md#3-local-tag-creation-lacks-a-post-write-inspection-failure-case).
**Target:** Publication preserves any verified local tag object obtained before
a later inspection failure. When its identity is unavailable after creation,
the error identifies the ref and possible retained local tag. Failed
verification remains a failure, with no push and a `not_published` remote
outcome.
**Rationale:** Recovery needs accurate partial evidence even when the initial
tag command succeeded and a later local command failed.
**Considered alternative:** Independent post-failure inspection or a new
creation-progress type can provide more machine-readable evidence, but adds
work or public surface. Existing results and contextual errors are sufficient
for the selected fix.
### A06. A partial build lists an artifact before hashing completes
Source: [interfaces](audit/09-interfaces.md#a-partial-build-result-can-contain-an-artifact-without-its-promised-digest).
**Target:** Every entry in the completed artifact list has its digest. A
hashing failure preserves earlier completed artifacts and identifies the
affected path in the error. Generated caller-owned files remain available for
recovery.
**Rationale:** An empty digest in a completed-output entry violates the current
partial-result contract even though the overall build returns an error.
**Considered alternative:** A separate compiled-but-unverified result state is
viable if consumers need richer recovery. The accepted choice retains the
existing completed-only model.
### A07. Temporary cleanup loses paths or silently discards failures
Sources: [resources §3](audit/05-resources.md#3-temporary-build-cleanup-failures-are-silently-discarded)
and [§4](audit/05-resources.md#4-a-failed-temporary-path-resolution-loses-the-cleanup-path).
**Target:** Owned temporary paths remain identifiable through allocation,
resolution, and cleanup failures. Removal failures are returned with the
affected path and any primary cause. Confirmed hosted outcomes remain intact:
`DeliveryComplete` can accompany a local cleanup error, and the CLI
distinguishes completed remote work from failed housekeeping. Caller-owned
prebuilt artifacts retain their lifecycle.
**Rationale:** Discarded errors and lost allocation paths can leave storage
behind without actionable recovery information.
**Considered alternative:** Warnings alone preserve primary success conventions
but can disappear when an optional writer is absent. Structured cleanup
metadata could support future automation; contextual returned errors are the
accepted representation.
### A08. Manual asset redirects have no finite hop budget
Source: [security §2](audit/07-security.md#2-medium-unique-asset-redirects-bypass-the-http-clients-finite-redirect-limit),
also referenced by the resource audit.
**Target:** An asset request follows at most ten redirects. Limit exhaustion is
unverifiable content. Exact-cycle detection, response-body closure, and
per-origin authorization remain in place. A longer-chain override requires
demonstrated consumer need.
**Rationale:** A finite hop budget bounds requests and retained URL history for
distinct-URL chains supplied by external content hosts.
**Considered alternative:** An operation deadline can bound elapsed time but
does not directly bound redirect history and is not automatically supplied by
the CLI. The selected protection is a redirect budget.
### A09. Mismatching assets are read beyond their expected size
Source: [security §3](audit/07-security.md#3-low-remote-content-verification-reads-beyond-the-known-matching-size),
also referenced by the efficiency and resource audits.
**Target:** Remote content comparison consumes at most the locally verified
expected size plus one byte, with overflow-safe counting. Shorter or longer
content conflicts; equal-length content still requires a matching digest. Read,
close, and cancellation failures retain their causes.
**Rationale:** Once size mismatch is established, further download and hashing
cannot produce a matching asset and can consume unbounded resources.
**Considered alternative:** Reported remote size or content length can be
absent or inaccurate. A global byte cap would also restrict legitimate
binaries. The accepted bound derives from local artifact facts.
### A10. Status-only Go output accumulates in memory
Source: [resources §1](audit/05-resources.md#1-go-command-output-is-retained-for-the-entire-execution).
**Target:** Status-only Go command diagnostics retain at most a 1 MiB tail,
with an explicit truncation notice on failure. Existing output destinations
receive the complete stream; operations without a destination retain their
quiet behavior. `Tool.Output` continues returning complete output. Process and
forwarding error identity are preserved.
**Rationale:** A bounded tail controls retained memory for long-running
commands without adding public configuration or temporary-log ownership.
**Considered alternative:** Full spooling retains complete failure history but
requires disk budgets, cleanup, and retrieval semantics. It remains a future
option if consumers require full captured failure history.
### A11. Forge JSON decoding has no response budget
Source: [resources §2](audit/05-resources.md#2-forge-json-responses-have-no-resource-bound).
**Target:** JSON response caps remain deferred pending a supported response
envelope for repository metadata, notes, and unpaginated attachment lists.
Assessment records available local sizing evidence and its limits. Context
cancellation and uncertain-write reconciliation remain unchanged.
**Rationale:** Source inspection demonstrates unbounded consumption, while the
reports establish neither representative maximum sizes nor a selected protocol
limit. Arbitrary caps would introduce a new compatibility restriction.
**Considered alternative:** Configurable limits with generous defaults provide
earlier containment but add policy and may reject supported responses. Sizing
evidence precedes that future decision.
### A12. Common Git observations have several owners
Sources: [complexity §2](audit/03-complexity.md#2-git-source-inspection-is-repeated-across-package-boundaries)
and [equivalence §1](audit/10-equivalence.md#1-source-state-observations-are-independently-implemented).
**Target:** The existing internal Git boundary owns common HEAD and
repository-wide status observations. Callers retain their observation timing
and decisions: clean-main candidate rules, dirty-build eligibility, and tagged
delivery checks stay distinct.
**Rationale:** One mechanics owner reduces duplicated object-view,
cancellation, and error propagation handling.
**Considered alternative:** Separate implementations keep individual repairs
small but retain parallel maintenance. A generalized release-state object would
combine different policies; narrow observations are the accepted abstraction.
### A13. Shared validation mixes sequencing and tree ownership
Source: [complexity §1](audit/03-complexity.md#1-shared-validation-combines-several-kinds-of-orchestration).
**Target:** Shared validation exposes a readable ordered Go-command sequence
and a committed-tree sequence that owns reader lifetime and close-error
attribution. Nil/empty selections, check order, failure precedence, and current
diagnostic identities are preserved.
**Rationale:** Separating these ownership responsibilities reduces distant
coordinated edits when checks change.
**Considered alternative:** Explicit blocks in one large function are viable
while stable. The accepted simplification uses private functions within
existing packages; a plugin registry has no demonstrated consumer need.
### A14. Internal build results duplicate normalized settings
Source: [complexity §3](audit/03-complexity.md#3-internal-build-results-retain-duplicate-settings).
**Target:** Internal build results have one normalized `Settings`
representation. Public `BuildResult` and private receipt fields keep their
existing contracts and explicit boundary conversions.
**Rationale:** Removing duplicate internal package/name/target fields prevents
drift without changing consumer types.
**Considered alternative:** A constructor can enforce duplicate-field
consistency but preserves redundant state and maintenance. A single internal
representation is accepted.
### A15. CLI routing contains complete command workflows
Source: [complexity §4](audit/03-complexity.md#4-cli-command-handling-interleaves-routing-and-command-specific-workflows).
**Target:** The CLI dispatcher delegates check/publication and build workflows
to named private handlers. Profile mapping, receipt invalidation order,
diagnostic context, and exit classifications retain their current
responsibilities.
**Rationale:** Command-specific workflows become easier to change and verify
without adding a command framework or public interface.
**Considered alternative:** Keeping the current switch is viable for small
repairs but continues interleaving routing with full workflows. Private
handlers are the accepted organization.
### A16. Receipts and delivery repeat declaration matching
Source: [equivalence §2](audit/10-equivalence.md#2-build-artifact-declarations-are-checked-independently-by-receipts-and-delivery).
**Target:** Receipts and delivery share pure target/filename declaration
matching in `internal/artifact`. Receipt schema/path/digest rules remain
CLI-owned; source-only handling and filesystem/content verification remain with
delivery after preflight.
**Rationale:** The same identity rule has two callers and one natural internal
owner. Its independent boundary rules remain explicit.
**Considered alternative:** Sharing only name generation avoids another helper
but duplicates uniqueness and matching logic. A narrow metadata-only helper is
accepted.
### A17. File-size exclusions scale with files times exclusions
Source: [efficiency](audit/02-efficiency.md#file-size-exclusions-are-scanned-for-every-selected-file).
**Target:** Exclusion indexing remains deferred pending benchmarks varying both
selected files and exclusion count. Production matching retains literal
component boundaries, module-relative normalization, shared inputs, and
first-oversized-file behavior.
**Rationale:** The O(N × E) scan is established, but practical slowdown has not
been measured; common small lists may favor the existing loop.
**Considered alternative:** A set plus ancestor lookups removes the
exclusion-count factor at an allocation/path-walking cost. A trie is viable for
deeper paths with additional complexity. Adoption remains an evidence-driven
future choice.
### A18. A canceled queued read can terminate a healthy blob session
Source: [concurrency §2](audit/04-concurrency.md#2-low-a-canceled-queued-blob-read-can-later-abort-a-healthy-session).
**Target:** A read canceled while queued is rejected after acquiring the reader
mutex and before it can change the healthy batch session. Current sequential
validation and mutex-based serialization remain in place.
**Rationale:** An already-canceled queued request can otherwise install a
callback that terminates a healthy child and affects subsequent requests.
**Considered alternative:** Cancellation-aware acquisition also provides prompt
queued cancellation but changes synchronization and expands its effective
concurrent-use contract. That expansion remains deferred until a caller needs
it.
### A19. Tests assert mechanisms beyond the protected behavior
Source: [testing §4](audit/08-testing.md#4-some-assertions-lock-in-incidental-internal-mechanisms).
**Target:** Tests protect parsing and source-relative resolution, artifact
integrity and ordering, and exact publication outcomes. Cache pointer identity,
exact binary-open counts, and successful-path remote query counts remain
implementation details. The documented single reconciliation query after a
failed push retains its separate contract.
**Rationale:** Mechanism assertions can reject correct refactoring while
failing to cover the observed correctness defects. Relevant performance
concerns are measured through benchmarks.
**Considered alternative:** A mechanism assertion is appropriate when it
protects a demonstrated resource problem that behavioral checks miss. The three
reported cases use behavioral evidence and benchmarks instead.
### A20. Two internal helpers have no production purpose
Sources: [dead code §1](audit/11-deadcode.md#1-committed-selection-identity-wrapper-has-no-callers)
and [§2](audit/11-deadcode.md#2-local-tag-inspection-helper-is-used-only-by-its-own-test).
**Target:** The unused `FromCommittedSelection` wrapper and test-only
`InspectLocalTag` helper are removed. Their live private implementations,
selection APIs, candidate tag guard, and useful full-tag/source assertions
remain. Tree accessors with meaningful tests or benchmarks remain supported
internally.
**Rationale:** These two helpers have no current production purpose or
documented retention need and are outside the public facades.
**Considered alternative:** A real shared-guard use could justify retaining
`InspectLocalTag`, but adds integration and ordering considerations. The
accepted standalone disposition is removal while preserving the live guard.
## Coverage of the source reports
Each original finding has a disposition above. Cross-references to findings in
other audits supply supporting context rather than additional work items.
| Report | Original finding → consolidated work |
| --- | --- |
| [01 Correctness](audit/01-correctness.md) | 1 → A01; 2 → A02; 3 → A03 |
| [02 Efficiency](audit/02-efficiency.md) | Exclusion scanning → A17; remote-read cross-reference → A09 |
| [03 Complexity](audit/03-complexity.md) | 1 → A13; 2 → A12; 3 → A14; 4 → A15 |
| [04 Concurrency](audit/04-concurrency.md) | 1 → A03; 2 → A18 |
| [05 Resources](audit/05-resources.md) | 1 → A10; 2 → A11; 3 and 4 → A07; cross-referenced shutdown/asset issues → A03/A08/A09 |
| [06 Error handling](audit/06-error-handling.md) | Retained local tag → A05 |
| [07 Security](audit/07-security.md) | 1 → A04; 2 → A08; 3 → A09 |
| [08 Testing](audit/08-testing.md) | 1 → A01/A02; 2 → A03; 3 → A05; 4 → A19 |
| [09 Interfaces](audit/09-interfaces.md) | Missing partial-artifact digest → A06 |
| [10 Equivalence](audit/10-equivalence.md) | 1 → A12; 2 → A16 |
| [11 Dead code](audit/11-deadcode.md) | 1 and 2 → A20 |

View File

@@ -1,191 +0,0 @@
# Functional correctness audit
Reviewed baseline: `dd0a557` on `main`, using Go 1.26.10 and Git 2.53.0 on
Linux. Findings describe the implementation at that commit; remediation remains
pending.
## Scope and evidence
The review covered the root orchestration facade, runtime version facade, CLI
and profile/receipt conversions, candidate capture and validation, module and
tag identity, Git publication and recovery, build/version verification, local
artifact declarations, shared hosted delivery, Gitea adaptation, preparation,
process execution, and maintained release/CI examples. Contracts were checked
against GoDoc, consumer and integration guides, architectural decisions, and
existing tests.
The following existing checks passed, with Go's test cache used where applicable:
```sh
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test ./...
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test -race ./...
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go run ./internal/apicheck
```
Temporary programs and repositories outside this checkout substantiated the
findings below. Publication probes used temporary local bare remotes;
cancellation probes used local processes and a simulated SSH executable.
Production code and repository tests remain unchanged. Live forge behavior was
outside this audit's verification scope.
## 1. High: publication can report success while leaving the tag unpublished
**Relevant code:** [Endpoint.PushTag](../../../internal/git/tag.go), particularly
the push invocation at line 139, and the success transition in
[publish.Publish](../../../internal/publish/publish.go), lines 167–174.
**Circumstances:** Git's effective configuration contains
`push.recurseSubmodules=only`. This can be a repository or inherited user
setting. A repository without submodules is sufficient to trigger the defect.
`PushTag` specifies an exact object-to-tag refspec and disables automatic
following of other tags, but inherits submodule push mode. Git's `only` mode
leaves the superproject unpushed and can exit zero. `Publish` treats that exit
as confirmed publication and immediately returns `published`/`complete`.
Reconciliation runs only after an error, so it never observes the missing tag.
Git documents this mode in its [push reference](https://git-scm.com/docs/git-push).
**Expected behavior:** Under the existing
[source publication contract](../../consumers/publish.md), a successful result
confirms publication of the captured annotated tag at the selected endpoint.
**Actual behavior and reproduction:** A temporary clean root-module candidate
on `main` contained a valid versioned release note, and its bare `origin` had
the same `main` commit. After setting `push.recurseSubmodules=only`, a public
`releasekit.Publish` call for `v1.0.0` returned:
```text
state=published phase=complete tag=v1.0.0 error=<nil>
```
An independent `git ls-remote origin refs/tags/v1.0.0` returned no records. The
annotated tag existed locally. The corresponding exact-object Git push exited
zero and printed only `Done`.
**Potential remediation:** Set `--recurse-submodules=no` explicitly on the
ReleaseKit-owned tag push, preserving its existing single-tag scope. A focused
public publication regression can configure `only`, invoke `Publish`, and
verify that the remote receives the exact captured tag object. Validate the
successful porcelain response if further evidence is needed; an additional
remote query on every successful push is a larger alternative to fixing this
specific inherited setting.
## 2. High: Git replacement objects can separate validated content from published source
**Relevant code:** candidate capture and local-state inspection in
[internal/candidate/check.go](../../../internal/candidate/check.go), committed
file/tree reads in [internal/git/source.go](../../../internal/git/source.go),
batched blob reads in [internal/git/blob_reader.go](../../../internal/git/blob_reader.go),
and tag creation/publication in [internal/git/tag.go](../../../internal/git/tag.go).
`ReadCommitFile` at line 255 reads through `git show`; `readLocalObjects` at
line 408 controls lazy fetching and transports but retains replacement-object
interpretation.
**Circumstances:** A local replacement ref maps the captured commit, tree, or
blob to different content. The working tree and index reflect that replacement
view. The authoritative remote has the original candidate commit and lacks
the local replacement mapping.
Git normally applies replacement refs during local object inspection, while
pack transfer uses the original objects. See the official
[replacement-object documentation](https://git-scm.com/docs/git-replace#_description).
ReleaseKit captures the original object ID, checks cleanliness and reads
committed content using the replacement view, and runs Go validation against
that working tree. It then creates and pushes a tag identifying the original
commit. Exact commit-ID and tag-object checks do not detect the content split.
**Expected behavior:** The selected module declaration, release note,
validated source, and published commit describe the same candidate, as required
by the [publication contract](../../consumers/publish.md). An affected local
replacement must be handled consistently before publication can succeed.
**Actual behavior and reproduction:** A temporary repository had original
candidate commit A on both local and remote `main`. A separate parentless
commit B contained different source and a different note with the same valid
product/version heading. After `git replace A B` and `git reset --hard HEAD`,
ordinary Git status was clean. A public `releasekit.Publish` call succeeded:
- Go validation inspected the replacement working tree.
- The annotated tag's message contained `Replacement note.`.
- Reading `v1.0.0:docs/releases/v1.0.0.md` directly from the bare remote returned
`Original note.`.
- The published tag identified A, whose source differed from the validated
working tree.
A second probe made A fail compilation with an undefined identifier and made B
compile successfully. `Publish` still returned `published` with no error, and
the bare remote retained A's unbuildable source. Running Git status with
replacement interpretation disabled exposed staged source and note differences
that the ordinary status check had hidden.
The same committed-object readers feed delivery and optional file checks, so
their handling should be reviewed alongside publication.
**Potential remediation:** Use an original-object Git view consistently for
ReleaseKit-owned candidate/object inspection and source-state comparisons,
for example through `GIT_NO_REPLACE_OBJECTS=1` or `--no-replace-objects` on
the relevant commands. Comparing the working tree against that view should
identify replacement-derived differences before validation/publication. Cover
both ordinary Git commands and the batched blob reader, plus the direct Git
inspection used by builds. Another viable approach is detecting replacements
that affect the selected source and returning a contextual error. Scope that
check to the identity/content mismatch; unrelated replacement refs do not
independently justify a repository-wide prohibition. Regression coverage should
verify original committed bytes and the published remote source, rather than
commit IDs alone.
## 3. Medium: inherited process pipes can defeat cancellation and recovery deadlines
**Relevant code:** [process.Run and newCommand](../../../internal/process/process.go),
lines 30–45 and 132–141; buffered Git execution in
[internal/git/remote.go](../../../internal/git/remote.go); and the bounded
reconciliation context in [publish.reconcilePush](../../../internal/publish/publish.go),
lines 177–181. Direct `exec.CommandContext` calls in
[internal/build/build.go](../../../internal/build/build.go) also merit the same
review.
**Circumstances:** A command launches a descendant that retains an inherited
stdout or stderr pipe, and ReleaseKit captures that stream through an
`io.Writer`. Git invoking an SSH process is a concrete example. Cancellation
kills the immediate command, while the descendant can keep the pipe open.
`newCommand` uses the default `exec.CommandContext` cancellation and leaves
`Cmd.WaitDelay` at zero. `Run` consequently waits for its output-copy goroutines
to observe EOF before checking and returning the context error. An inherited
descriptor can delay EOF indefinitely. The Go standard library describes this
behavior and its existing mitigation in
[Cmd.WaitDelay](https://pkg.go.dev/os/exec#Cmd).
**Expected behavior:** Canceled preparation and inspection operations return
their cancellation outcome, and the documented bounded authoritative
reconciliation can finish with an unresolved outcome when its deadline expires.
**Actual behavior and reproduction:** A temporary program called the public
`PrepareTaggedCheckout` API with a 500 ms deadline and a local simulated SSH
transport that launched `sleep 3` while retaining its standard streams. The
SSH executable was reached; the API returned after approximately 3.01 seconds,
preserving `context.DeadlineExceeded` only after the descendant exited. A
separate direct `process.Run` probe with buffered output, a 200 ms deadline,
and `sh -c 'sleep 3 & wait'` similarly took approximately 3.01 seconds. A
descendant that keeps the descriptor open indefinitely leaves the operation
waiting indefinitely. The reconciliation consequence follows from its use of
the same Git/process boundary; a stalled reconciliation was not separately
run for its full 30-second timeout.
**Potential remediation:** Give captured command I/O a bounded shutdown path
after cancellation, using standard `exec.Cmd.WaitDelay` where applicable, and
preserve the existing context-error identity. Review explicitly owned pipes
and direct build subprocesses so their cleanup does not wait for EOF before
the cancellation path can close the pipe. Add controlled regression coverage
with a ready descendant retaining a descriptor; ordinary single-process
cancellation tests do not exercise this case. Keep the change focused on
returning cancellation and preserving the existing recovery bound; a general
descendant-process supervision contract would require a separate decision.
## Recommended order
Address findings 1 and 2 before the next release because they can report a
successful release without the required tag or validated source. Then address
finding 3 at the shared process boundary and verify its Git and build callers.
The existing suite and facade-boundary checks remain useful, with focused
regressions needed for these uncovered cases.

View File

@@ -1,35 +0,0 @@
# Efficiency audit
Reviewed baseline: `812c24d` on `main`. This review examined the Go facade,
CLI, candidate validation, committed-tree and path inspection, build,
publication, and hosted-delivery paths for correctness and avoidable scaling
costs. Complexity estimates below describe the code, not measured timings.
Previously recorded correctness and security findings remain in their
respective audit documents.
## File-size exclusions are scanned for every selected file
**Relevant code:** [checkFileSize and excludedByPath](../../../internal/validation/file_size.go).
The public [file-size settings](../../../check.go) accept literal file and
directory exclusions.
For each selected blob, `checkFileSize` calls `excludedByPath`, which scans
the normalized exclusions until it finds a match. With N selected blobs and E
exclusions, inputs with no matches require N × E path comparisons; when N and
E grow together, this is quadratic, before Git is asked for any blob sizes.
String comparison also depends on path length. This is a potential scaling
cost for consumers with large repositories and extensive exclusion lists; the
review did not measure its practical impact.
**Potential refactoring:** Index normalized exclusions once in a hash set,
then test each file path and its directory ancestors, including the special
`.` exclusion. This takes expected O(E) indexing plus O(N × D) lookups for
maximum path depth D, apart from string-processing costs, instead of scanning
all E exclusions for each file. A component trie is another option if path
processing warrants it. Preserve literal component-boundary matching,
module-relative normalization, the order of selected files, and the first
oversized-file result. Existing exclusion cases cover the semantics; add
targeted cases for shared prefixes and nested directories if refactored.
The [security audit](07-security.md#3-low-remote-content-verification-reads-beyond-the-known-matching-size)
separately records avoidable remote asset reads beyond the expected size.

View File

@@ -1,85 +0,0 @@
# Complexity audit
Reviewed baseline: `620c832` on `main`. The review used the local codebase
knowledge graph's cognitive and cyclomatic complexity estimates to identify
candidates, then inspected their code, callers, package boundaries, and
documented contracts. Scores are heuristics, not defect or refactoring
thresholds. No empirical performance measurements or application tests were
run for this documentation-only review. Existing correctness, concurrency,
security, and efficiency findings are recorded in the neighboring audits.
## 1. Shared validation combines several kinds of orchestration
[validation.Run](../../../internal/validation/validation.go) has a graph
cognitive score of 78 and cyclomatic score of 37. It validates settings,
acquires the Go tool, runs tests, vet and compilation, decides whether to
load a committed tree, chooses an error identity for a failed tree load, runs
file checks, and attributes a deferred reader-close error. The tree-load
condition, the reverse-priority error selection, and the ordered check blocks
each separately enumerate selections. Adding a check therefore requires a
maintainer to update several distant parts while preserving ordering and
close-error attribution.
Consider separating the Go-command sequence and the tree-check sequence into
small private functions, with the tree owner responsible for closure and its
failure attribution. Keep the selected checks and their order visible in one
place; a generic plugin system is unnecessary. Preserve nil versus empty shared
file selections, the current first-failure identities and precedence, and
the fact that a close failure replaces success only after a content read.
## 2. Git source inspection is repeated across package boundaries
[build.BuildResolved](../../../internal/build/build.go) uses its own
`gitCommit` and `cleanSource` subprocesses before and after building.
[candidate](../../../internal/candidate/check.go) separately reads branch,
commit and status, while [git](../../../internal/git/source.go) has
`CaptureHead` and another tagged-source inspection path. These overlap in
commands and interpretation but use different execution and error handling.
Understanding a source-state change or a subprocess fix requires tracing all
three owners. The existing [correctness audit](01-correctness.md) identifies
replacement-object and cancellation concerns that make this duplication
particularly relevant.
Consider moving the common commit and worktree-status primitives behind the
existing internal Git boundary, with callers composing only the observations
they need. Candidate checking still needs branch and tag rules; building still
records dirty source rather than rejecting it; delivery still checks a tag.
Preserve these different contracts, error context, cancellation behavior, and
the timing of before/after observations. Coordinate any such refactor with the
existing correctness work instead of treating code consolidation as its fix.
## 3. Internal build results retain duplicate settings
[internal/build.Result](../../../internal/build/build.go) stores normalized
`Settings` alongside separate `MainPackage`, `BinaryName`, and `Targets`
fields containing the same information. `BuildResolved` initializes both,
and [facade conversion](../../../build.go) reconstructs both on input while
reading `Settings` on output. Delivery uses `Settings` for identity and asset
validation. This creates two sources of truth inside the implementation and
additional work when a build setting is added; a future caller could populate
them inconsistently.
Consider retaining `Settings` as the single internal representation and
updating internal build execution to use it. Keep the existing public
`BuildResult` fields and the CLI's private receipt format at their respective
boundaries; their distinct types and conversions serve deliberate ownership
and serialization requirements. Verify partial build results and prebuilt
delivery remain unchanged. This is a simplification opportunity, not an
observed mismatch in current callers.
## 4. CLI command handling interleaves routing and command-specific workflows
[run](../../../cmd/releasekit/main.go) has a graph cognitive score of 85 and
combines routing with profile loading, publication and check reporting, build
execution, receipt invalidation and verification, and exit-code decisions.
The same file also contains delivery preparation and receipt conversions.
The build branch alone has several separate failure paths that must be read
alongside its success and partial-result output; changes to one command add
noise to the central dispatcher.
Consider keeping `run` as a small dispatcher and moving the check/publish and
build command workflows into named private handlers, grouped by responsibility.
Keep receipt invalidation before build-error reporting, existing messages and
exit statuses, and the CLI's use of the public orchestration facade. Existing
delivery handlers provide a local example of this separation. This does not
require a new command framework or public interfaces.

View File

@@ -1,169 +0,0 @@
# Concurrency audit
Reviewed baseline: `d884ad7` on `main`, using Go 1.26.10 and Git 2.53.0 on
Linux. The findings below remain pending. Finding 1 overlaps an existing
correctness finding; finding 2 is an additional internal-helper weakness.
## Architecture and scope
The root facade coordinates candidate checking, publication, builds, preparation,
and hosted delivery. The CLI supplies profiles, credentials, output, and process
signals. Internal packages own the operation state and external effects;
`internal/forge` separates shared delivery policy from the Gitea adapter.
The `buildinfo` facade has a small, independent runtime implementation.
Most orchestration is sequential. Go settings owners, candidate-tree caches,
delivery result slices, and backend instances belong to individual operations.
The principal concurrency mechanisms are:
- `internal/process` uses context-driven subprocess cancellation, standard
library I/O goroutines, and `sync.Once` for piped-process waiting.
- `internal/git.BlobReader` serializes batch exchanges with a mutex and joins
each read's cancellation callback before releasing the exchange.
- `internal/gotool` serializes combined stdout/stderr capture and forwarding
with a mutex. Captured bytes are consumed after process completion.
- `internal/gitea` streams multipart uploads through an `io.Pipe` and a worker
goroutine, joining that worker before returning the borrowed content reader.
- `internal/runtimeinfo` caches fallback version discovery with
`sync.OnceValue`; the injected version is initialized at link time.
The review examined those ownership and shutdown paths, callback sequencing,
shared caches and output, build cleanup, publication guards/reconciliation,
delivery recovery, and signal propagation. Operation-local helpers such as the
Go settings owner and candidate tree are used sequentially by current callers;
their mutable maps alone do not establish a concurrency defect.
## Verification
The repeated, uncached race checks passed:
```sh
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test -race -count=5 \
./internal/process ./internal/git ./internal/gitea ./internal/runtimeinfo
```
The preceding correctness audit also passed the full ordinary and race-enabled
suites and compiled API/dependency checks. Production code and tests are
unchanged between those checks and this review's baseline.
Temporary race-enabled programs outside this checkout reproduced the two
findings below without race-detector diagnostics. Controlled Git substitutes
provided readiness and release signals; short-lived descendants exercised
inherited descriptors. Local HTTP/1.1 and HTTP/2 probes of early upload
rejection returned promptly and produced no additional finding. These probes
used local fixtures and placeholder credentials.
No data race or unjoined multipart-worker leak was identified in the reviewed
current orchestration paths. Race detection complements the liveness evidence;
a clean race run does not detect a blocked operation.
## 1. Medium: inherited descriptors can keep canceled operations and reader shutdown blocked
**Relevant code:** [process.Run, newCommand, and PipedProcess.Wait](../../../internal/process/process.go),
buffered Git execution in [internal/git/remote.go](../../../internal/git/remote.go),
and [BlobReader.Close](../../../internal/git/blob_reader.go), especially the
stdout drain at line 110 followed by the child wait. The owning cleanup runs
from [shared validation](../../../internal/validation/validation.go).
**Circumstances and mechanism:** A subprocess launches a descendant that
inherits stdout or stderr. Cancellation kills the immediate process, while
the descendant retains the descriptor. Buffered command execution waits for
its standard library I/O workers to receive EOF. The batched reader's normal
shutdown closes stdin, drains stdout to EOF, and then waits for the child.
Both paths can remain blocked after the governing context is canceled.
This is the same underlying issue recorded as finding 3 in the
[correctness audit](01-correctness.md#3-medium-inherited-process-pipes-can-defeat-cancellation-and-recovery-deadlines).
That audit reproduced delayed cancellation through public
`PrepareTaggedCheckout` using a simulated SSH transport. This concurrency pass
additionally exercised the explicitly owned batch-reader shutdown path.
**Additional reproduction:** A controlled `git cat-file --batch` substitute
returned a valid three-byte blob. After stdin reached EOF, it launched
`sleep 3` with inherited descriptors and exited. The reader's operation context
had a 500 ms deadline. The initial read succeeded, but `BlobReader.Close`
returned only after approximately 3.005 seconds, with the deadline error then
preserved. The probe ran under the race detector and completed after its
descendant exited. A descendant retaining the descriptors indefinitely can
keep this shutdown waiting indefinitely.
**Consequences:** Cancellation can leave validation or preparation occupied
beyond its deadline, delay temporary-output cleanup, and prevent publication
reconciliation from returning its documented bounded unresolved outcome.
`BlobReader.Close` holds the reader mutex during its drain, so other readers
and repeated closes also wait for that shutdown to finish.
**Potential remediation:** Give subprocess I/O a bounded cancellation/shutdown
path. Use the standard `exec.Cmd.WaitDelay` mechanism where applicable, and
verify that explicitly owned stdin/stdout pipes also close when their operation
is canceled. Coordinate cancellation callbacks, pipe closure, and `Wait` so
the child and workers are joined before the owning operation returns. Apply
the same review to direct subprocess calls in
[internal/build](../../../internal/build/build.go). Preserve existing context
error identity and publication uncertainty semantics.
Regression coverage should include a ready descendant retaining a descriptor
during both buffered command execution and batch-reader cleanup. The existing
single-process cancellation tests pass. This finding calls for completing the
selected cancellation behavior; it does not require a general service for
supervising every descendant process. Treat this and correctness finding 3 as
one remediation unit.
## 2. Low: a canceled queued blob read can later abort a healthy session
**Relevant code:** [BlobReader.Read](../../../internal/git/blob_reader.go),
lines 44–88. It checks the request context before acquiring `reader.mu`, then
starts or reuses the session and registers the cancellation callback after
acquiring the mutex. It does not recheck the request context between those
steps. `abortLocked` records any exchange cancellation as the reader's terminal
error.
**Circumstances:** Two goroutines call `Read` on one reader with independent
request contexts. The first holds the mutex during a slow exchange. The
second passes its initial context check, waits for the mutex, and is canceled
while waiting. Its cancellation callback is not registered during that wait.
Once the first exchange finishes, the second acquires the mutex and proceeds
with an already-canceled context. Its newly registered `context.AfterFunc`
can immediately cancel the healthy child and close its pipes. The read then
records a terminal cancellation, affecting subsequent uncanceled requests.
**Reproduction:** A controlled batch process signaled when the first exchange
was active and waited for an external release signal. An observed request
context established that the second read had passed its initial context check
before cancellation. The canceled request remained blocked during a 100 ms
observation window. After releasing the first exchange:
```text
first read error: <nil>
second read canceled: true
next uncanceled read inherits cancellation: true
```
The race-enabled probe produced no data-race diagnostic. The defect concerns
the ordering of cancellation and session ownership.
**Consequences and current reach:** Queued canceled work can wait behind an
unrelated exchange and then terminate its otherwise usable shared reader.
The helper advertises serialized reads and implements mutex-based sharing,
but current candidate validation calls it sequentially. This is an internal
concurrent-use weakness; the audit did not identify a current public operation
that issues these overlapping reads on one reader.
**Potential remediation:** At minimum, recheck `ctx.Err()` immediately after
acquiring the mutex and before starting an exchange or registering a callback
that can cancel the child. This prevents a request canceled while queued from
changing the healthy session. If queued reads should return promptly on their
own cancellation, use cancellation-aware acquisition of the exchange slot.
Keep current candidate-tree traversal sequential while assessing that need;
this finding supplies no reason to parallelize validation or add locks to its
operation-local caches. A focused helper regression can verify the healthy
first exchange and a later uncanceled read after cancellation of queued work.
## Recommendation
Complete the existing subprocess-cancellation remediation first, including the
explicit batch-reader shutdown path. Address the queued-read context recheck
as a small internal correctness improvement; select cancellation-aware queue
waiting according to the helper's intended concurrent-use contract. Retain the
current operation-local architecture and joined worker lifetimes.

View File

@@ -1,101 +0,0 @@
# Resource management audit
Reviewed baseline: `9545bb0` on `main`. This review traced subprocesses,
buffers, committed blobs, temporary storage, HTTP responses, upload workers,
and cancellation across the public facade, CLI, and internal packages. The
findings below are based on source inspection; no load measurements or new
tests were run. The existing [concurrency audit](04-concurrency.md) records
inherited subprocess descriptors delaying cancellation and batch-reader
shutdown, and the [security audit](07-security.md) records unbounded unique
asset redirects and remote asset reads beyond the expected size. Those remain
separate remediation items.
## 1. Go command output is retained for the entire execution
**Relevant code:** [gotool.execute and combinedWriter](../../../internal/gotool/gotool.go),
and its callers in [shared validation](../../../internal/validation/validation.go)
and [dependency preparation](../../../internal/preparation/preparation.go).
Every write to the combined stdout/stderr writer is appended to an in-memory
buffer, even for status-only `Run` calls and even when output is forwarded to
a caller's writer. A large or long-running Go test suite, compiler, or module
download with voluminous diagnostics therefore retains memory proportional to
all output until the process exits. On failure, formatting the full output in
an error can add another large allocation. Multiple simultaneous operations
multiply that consumption and can exhaust the process or host. The
[internal guide](../../internal/gotool.md#command-output-ownership) already
documents the full-capture choice and its memory tradeoff.
**Potential approaches:** Evaluate spooling status-only diagnostics to a
temporary file while streaming output, with explicit cleanup and error
handling, or select a bounded diagnostic policy after deciding how much error
output callers should retain. Preserve full output where `Tool.Output` needs
it, forwarding behavior, error identity, and useful failure diagnostics; a
truncation policy changes the current diagnostic behavior and needs a deliberate
decision. Avoid replacing memory exhaustion with unbounded, unowned disk usage.
## 2. Forge JSON responses have no resource bound
**Relevant code:** [getJSON, getRelease, postRelease, and decodeJSON](../../../internal/gitea/release.go),
including attachment listing in [assets.go](../../../internal/gitea/assets.go)
and upload response decoding in [upload.go](../../../internal/gitea/upload.go).
Successful HTTP response bodies are decoded directly without a byte limit.
The decoder buffers complete JSON values, including fields that are not used,
and attachment lists and release bodies are retained in memory. A faulty or
compromised forge returning a very large value or continuously streaming an
unfinished value can consume substantial memory or keep the operation active.
The second decode waits for EOF to reject trailing values; a response that
never ends can also prevent completion. The default client has no overall
timeout, although callers can cancel through the operation context. Response
bodies are closed when these functions return; the issue is consumption before
they can return, not a missing close.
**Potential approaches:** Choose response-size and time budgets appropriate
to the supported forge contract, potentially configurable for large release
notes or attachment lists. Enforce a distinguishable limit rather than
silently accepting truncated JSON, and retain context cancellation and
malformed-response classification. For a failed create or upload response,
preserve uncertain-write reconciliation: rejecting a response does not prove
that the remote write failed. Any default limit is a compatibility decision,
not an implied existing restriction.
## 3. Temporary build cleanup failures are silently discarded
**Relevant code:** [candidate build callback](../../../internal/build/candidate.go),
[native smoke build](../../../internal/build/build.go), and
[managed delivery build](../../../internal/delivery/delivery.go). Each defers
`os.RemoveAll` without inspecting its error.
If removal fails, for example because permissions or filesystem availability
change, the operation can return success or an unrelated failure while leaving
generated binaries and their directories behind. Repeated builds can consume
disk space, and an operator is not told where to clean up. The delivery guide
describes managed output as removed when delivery returns. This is distinct
from the previously recorded cancellation hang, which can prevent cleanup
from being reached at all.
**Potential approaches:** Capture cleanup errors and report the affected path,
joining them with an existing failure where useful. Decide how to expose a
cleanup failure after remote delivery has succeeded without misrepresenting the
already completed remote state or encouraging an unsafe retry. Keep caller-owned
prebuilt artifacts outside this cleanup path. Apply the same review to the
temporary tag-message and receipt files, which also discard removal errors.
## 4. A failed temporary-path resolution loses the cleanup path
**Relevant code:** [tempdir.Outside](../../../internal/tempdir/tempdir.go),
at both calls to `filepath.EvalSymlinks` after `os.MkdirTemp`.
The resolved value is assigned back to `directory` before checking the error;
on a resolution error the cleanup uses that returned value instead of the
saved `created` path. If the returned path is empty or otherwise unusable,
the newly created directory is not removed. This can occur if path resolution
fails after creation, for example after a concurrent filesystem or permission
change. The helper returns an error without telling the caller the directory's
name, so the caller cannot perform cleanup. The directory is initially empty;
the impact is leaked temporary entries rather than leaked build artifacts.
**Potential approach:** Keep the created path until resolution succeeds and
use it for error-path removal, including the fallback allocation. Preserve the
original resolution error and consider reporting a cleanup error as well.

View File

@@ -1,54 +0,0 @@
# Error-handling audit
Reviewed baseline: `3754242` on `main`. The review traced public errors and
partial results through candidate validation, Git publication, build and
preparation, subprocess execution, and hosted delivery. Existing findings about
subprocess cancellation, publication integrity, temporary cleanup, and remote
resource consumption are retained in the [correctness](01-correctness.md),
[concurrency](04-concurrency.md), [resource](05-resources.md), and
[security](07-security.md) audits rather than duplicated here.
The existing `internal/delivery`, `internal/git`, and `internal/gitea` tests
passed with the Go test cache. An isolated temporary repository, a copy of the
Git and process packages, and a Git wrapper were used to exercise the finding
below; no production source or repository tests were modified.
## A post-creation inspection failure hides a retained local tag
**Relevant code:** [CreateAnnotatedTag](../../../internal/git/tag.go),
[publish.Publish](../../../internal/publish/publish.go), and the public
[PublicationResult](../../../publish.go).
**Failure condition:** Git successfully creates the annotated local tag, but
a subsequent `cat-file` or `rev-parse` inspection fails, for example because
the process is interrupted or the local Git operation encounters an error.
`CreateAnnotatedTag` returns an empty object ID on every such error. Its caller
returns immediately without setting `PublicationResult.TagObject`. The local
tag is not removed, and the next publication attempt's candidate check rejects
that existing tag. No remote push has been attempted at this point.
**Expected and actual behavior:** `PublicationResult.TagObject` is documented
as the local annotated tag object ID when created, and publication results are
intended to give callers evidence for recovery. The result instead reports
`not_published` and the tag-creation phase with an empty object field even
though a local tag may remain. The returned error identifies the failed
inspection but does not explain that tag creation already succeeded. An
operator may have to discover the tag independently before deciding how to
recover; the absence of an object ID is not evidence that no tag exists.
**Evidence:** In a temporary repository, a wrapper delegated real Git commands
except for the post-creation `cat-file -t` inspection, which returned an error.
`CreateAnnotatedTag` returned an empty object ID and an error, while an
independent Git query confirmed the local tag existed. The public result's
empty field follows from the caller's immediate error return; the probe did
not run the complete public publication workflow.
**Potential approaches:** Preserve any verified object ID obtained before a
later failure and return it with the error. When creation succeeded but the
object could not be inspected, report explicitly that the local tag may exist
and identify its ref for manual inspection. Keep the failed verification and
original cause visible, never treat the tag as publishable without the existing
checks, and avoid deleting or changing a ref whose current ownership is not
established. A focused regression can force an inspection failure after
successful creation and check both the local ref and returned recovery
information.

View File

@@ -1,200 +0,0 @@
# Security audit
Reviewed baseline: `1b39e71` on `main`, using Go 1.26.10 and Git 2.53.0 on
Linux. The three findings below remain pending.
## Architecture and threat scope
The root facade coordinates candidate validation, source publication, builds,
preparation, and delivery. `buildinfo` supplies independent runtime version
access. Internal packages own Git, Go execution, filesystem operations,
artifact verification, shared delivery policy, and the Gitea protocol adapter.
The CLI owns configuration, credential loading, output, and exit statuses.
Repository source and release profiles are trusted execution inputs: candidate
checks run Go tests, configured checks invoke selected programs, and binary
builds execute the application's version command. ReleaseKit runs with the
operator's permissions. Its offline Go settings control module/toolchain
downloads. Repository commands retain the operator's filesystem and network
permissions.
This review assessed that implemented operating model under the
[architecture policy](../../policy/architecture.md#trust-boundaries-and-threat-scope).
Security-relevant external inputs include remote Git observations, forge HTTP
responses, attachment download URLs and redirects, and downloaded bytes.
Cross-origin asset downloads are an explicitly supported
[integration behavior](../../integrations/gitea.md). The audit evaluated both
credential origin checks and resource consumption while following those
externally supplied URLs.
The review covered credential supply and diagnostics, HTTP authentication and
redirects, preflight evidence, command arguments, committed path/symlink
resolution, build and receipt paths, artifact integrity, temporary-file
ownership, CI bootstrap examples, and dependency advisories. Local privileged
attackers and malicious code already authorized to run as the operator were
outside the assessed boundary.
## Verification and observed protections
The following checks passed:
```sh
GOTOOLCHAIN=local go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 ./...
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test -count=1 \
./internal/gitea ./internal/artifact ./cmd/releasekit/internal/cli
```
`govulncheck` reported `No vulnerabilities found.` for the host build using the
current Go vulnerability database. This is a known-vulnerability result for
the scanned build; it does not establish the security of every supported
platform or older compatible toolchain. See the Go project's
[vulnerability management documentation](https://go.dev/doc/security/vuln/).
The preceding audits' full ordinary/race suites and facade-boundary checks
cover the same unchanged production code. A targeted check of 191 tracked files
at this baseline found no private-key headers or credential-bearing HTTP URL
patterns. Ignored credential files were outside that content scan.
Isolated local programs reproduced the findings below. Their credentials were
placeholders; the Git credential probe stopped before any network operation.
HTTP probes used local servers. Production code and repository tests remain
unchanged.
The review confirmed useful existing protections: direct argument vectors for
commands, committed-tree path boundary handling, strict receipt declarations,
artifact hashing before hosted writes, explicit destination selection, and
read-only preflight with documented permission-evidence limits. Asset redirects
are handled manually, and the Gitea token was withheld from the separate asset
origin in the redirect probe. The findings concern other aspects of those
boundaries.
## 1. Medium: publication diagnostics expose credentials embedded in Git URLs
**Relevant code:** [publish.Publish](../../../internal/publish/publish.go),
lines 106–108; publication failure output in
[cmd/releasekit/main.go](../../../cmd/releasekit/main.go), line 63; and URL-bearing
errors in [internal/git/remote.go](../../../internal/git/remote.go),
[internal/git/tag.go](../../../internal/git/tag.go), and
[publication reconciliation](../../../internal/publish/publish.go).
**Vulnerability and circumstances:** Git supports HTTPS remotes containing
userinfo credentials, including an access token in the password position.
ReleaseKit retains that complete effective URL and interpolates it directly
into errors. The CLI also prints `PublicationResult.Endpoint` verbatim on a
publication failure. An ordinary URL mismatch, unavailable remote, or later
publication failure can therefore copy the credential from local configuration
into terminal transcripts, CI logs, or a shared diagnostic report.
**Reproduction:** A temporary clean candidate used an HTTPS `origin` containing
a placeholder password, with a different credential-free `ExpectedPushURL`.
A public `Publish` call returned `ErrUnexpectedPushURL`. Both the error text
and returned endpoint contained the complete placeholder password. This
happened before validation or network access. Inspection of the CLI's failure
formatter confirms that both values flow into its output.
**Impact:** Readers of diagnostic output can obtain a working Git password or
access token even when they have no access to the operator's local repository
configuration. The token's permissions determine the resulting repository
access. This exposure concerns Git URL credentials; the probe did not find a
leak of the separately supplied Gitea delivery token.
**Potential remediation:** Introduce a credential-safe URL representation at
diagnostic boundaries and use it consistently in library errors and CLI output.
The standard library's [URL.Redacted](https://pkg.go.dev/net/url#URL.Redacted)
handles passwords; review username-carried tokens when choosing userinfo
redaction. Check wrapped Git diagnostics as well as ReleaseKit-owned messages.
Retain the original URL for transport and exact destination comparison, and
preserve error identity. `PublicationResult.Endpoint` currently promises the
captured effective URL: keep that contract explicit, document its sensitivity,
and sanitize its CLI rendering rather than silently changing transport or
comparison semantics. A regression should exercise a credential-bearing
remote and verify safe errors/output while exact comparison remains intact.
## 2. Medium: unique asset redirects bypass the HTTP client's finite redirect limit
**Relevant code:** [Client.httpClient](../../../internal/gitea/release.go),
which disables automatic redirects, and
[Client.openAsset](../../../internal/gitea/assets.go), lines 58–85.
**Vulnerability and circumstances:** The manual redirect loop retains every
visited URL and rejects an exact repeated URL. It has no hop limit. A content
server can continually redirect to a new query value or path, so every request
passes cycle detection and adds another retained string. The ordinary Go
client's redirect bound is bypassed because each hop is issued as a separate
`Do` call with automatic redirection disabled. The CLI uses a signal-driven
context without an automatic delivery deadline, and the default HTTP client
has no total timeout.
A compromised external attachment host, or an accidentally recursive redirect
rule that keeps changing the URL, can trigger this during asset inspection.
This scenario uses the supported cross-origin download path; it requires no
ability to execute repository code or obtain the delivery token.
**Reproduction:** A local forge fixture returned an attachment URL on a second
local server. That server issued 64 redirects with distinct query values and
then returned content. `Backend.OpenAsset` completed after 65 asset requests.
An ordinary Go HTTP client rejected the same chain under its default redirect
policy. The fixture separately confirmed that no Gitea authorization header
reached the second origin. The standard client's default policy is documented
under [http.Client](https://pkg.go.dev/net/http#Client).
**Impact:** A hostile or faulty content host can occupy the delivery process
and generate requests indefinitely. The visited-URL map grows with the chain,
creating a memory-exhaustion path as well as network and runner-time costs.
The probe demonstrated excess redirects, not an out-of-memory crash.
**Potential remediation:** Apply a finite request-local redirect budget while
retaining the current per-origin credential checks and response-body cleanup.
The normal Go limit of ten redirects is a proportionate starting point.
Choose and document the budget deliberately during remediation. If actual
consumer workflows need longer chains, evaluate an explicit override alongside
that decision. A configured deadline can supplement this, while a hop budget
also bounds retained URL history. Regression coverage should include distinct-URL
chains, ordinary redirects, exact cycles, and authorization across origins.
This finding does not call for prohibiting supported external content hosts.
## 3. Low: remote content verification reads beyond the known matching size
**Relevant code:** [inspectRemoteAssets and remoteAssetDigest](../../../internal/delivery/remote_assets.go),
particularly lines 80 and 102–113. Each expected artifact already carries its
local verified `Size`, but the download comparison passes only the remote
handle and hashes the stream to EOF.
**Vulnerability and circumstances:** An attachment response can be much larger
than the declared local asset, or continuously supply bytes without ending.
After the response exceeds the expected file size, it cannot match the local
declaration, but ReleaseKit continues downloading and hashing it. An external
content host controlling this response can prolong useful-looking activity and
consume bandwidth and CPU for a result that is already known to conflict.
**Reproduction:** A public `Deliver` call used a valid temporary tagged checkout
and a caller-owned three-byte prebuilt artifact. The local forge fixture listed
the expected filename and a reported size of three bytes, while its download
endpoint returned 65,536 different bytes. A counting response-body wrapper
confirmed that delivery read all 65,536 bytes before reporting `conflict`.
No hosted writes occurred. The stream was finite to keep the probe bounded.
**Impact:** A small expected asset can induce arbitrarily larger downloads and
hashing work. This is a resource-consumption weakness; the finite probe still
reported the content conflict correctly. It is lower priority than the
credential exposure and redirect-history growth.
**Potential remediation:** Pass the locally verified expected size into remote
content verification and consume at most that size plus one byte. Detecting an
extra byte establishes a conflict; a shorter stream also conflicts. Keep the
digest comparison for equal-length streams, close the stream on every path,
and retain inspection errors/cancellation causes. Derive the bound from the
local declaration, preserving the supported artifact sizes and avoiding an
arbitrary global download cap. This addresses excess bytes; a stalled server
before that bound still follows the operation's cancellation/timeout behavior.
## Related work and recommendation
The [correctness audit](01-correctness.md) tracks publication integrity issues,
and the [concurrency audit](04-concurrency.md) tracks subprocess shutdown and
queued-reader cancellation. Their remediation remains relevant to release
integrity and availability.
Prioritize credential-safe diagnostics, then bound manual redirect handling
and remote mismatch reads. Retain the existing source/profile trust model,
explicit forge selection, token origin checks, and additive delivery policy.

View File

@@ -1,98 +0,0 @@
# Test coverage and effectiveness audit
Reviewed baseline: `3d7a691` on `main` against the
[testing policy](../../policy/testing.md). The review considered public and
package contracts, failure and recovery paths, fixture realism, and the cost
of implementation-coupled assertions. Coverage is a way to locate questions,
not evidence that every exercised behavior is correct. No production code or
tests were modified for this audit.
The offline instrumented suite passed with
`GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test -coverpkg=./... -coverprofile=/tmp/releasekit-audit-coverage.out ./...`.
`go tool cover -func` reported 86.0%
aggregate statement coverage across instrumented packages. Child executables
and platform-specific paths are not necessarily represented by that aggregate;
it does not measure branch coverage or assertion strength.
## 1. Publication fixtures omit Git settings that change what is published
The public [publication success test](../../../publish_test.go) checks the
remote tag object, peeled commit, message, and unrelated refs using a local
bare remote. However, its ordinary Git configuration does not exercise
`push.recurseSubmodules=only`, under which the
[correctness audit](01-correctness.md#1-high-publication-can-report-success-while-leaving-the-tag-unpublished)
reproduced a successful result with no remote tag. The same test checks object
IDs and tag-message bytes, but its fixture has no Git replacement objects;
the [second correctness finding](01-correctness.md#2-high-git-replacement-objects-can-separate-validated-content-from-published-source)
demonstrates that the validated contents and remote contents can differ while
the recorded commit ID remains the same.
When these defects are addressed, add focused public-boundary regressions with
a real local bare remote. For the inherited push mode, assert that a successful
result corresponds to the exact remotely present tag object. For replacement
objects, compare the actual validated and remotely published source and note
bytes, or assert a contextual rejection before publication. Preserve the
existing ordinary success case; it protects different important behavior.
## 2. Cancellation fixtures do not cover inherited subprocess descriptors
[process cancellation tests](../../../internal/process/process_test.go) and
[candidate reader lifecycle tests](../../../internal/validation/tree_lifecycle_test.go)
exercise cancellation of a ready child and check error identity and cleanup.
Their blocked children do not leave a descendant holding an output descriptor
after the direct child is killed. The independent probes recorded in the
[concurrency audit](04-concurrency.md#1-medium-inherited-descriptors-can-keep-canceled-operations-and-reader-shutdown-blocked)
show that both buffered execution and batch-reader shutdown can then remain
blocked past cancellation, despite those tests passing.
Add controlled regressions when repairing the shared shutdown path: establish
that a descendant holds a descriptor, cancel only after readiness, and verify
bounded return, preserved context identity, and cleanup. Exercise both the
buffered subprocess boundary and the explicitly owned batch-reader path; retain
one public preparation or publication-recovery check if needed to show the
shared behavior reaches callers. Avoid treating arbitrary descendant-process
supervision as an additional contract.
## 3. Local tag creation lacks a post-write inspection failure case
The [publication tests](../../../publish_test.go) cover successful exact tag
publication, a rejected push with the local tag retained, and a changed tag
before push. They do not cover failure of the inspection commands immediately
after successful local tag creation. The isolated probe in the
[error-handling audit](06-error-handling.md#a-post-creation-inspection-failure-hides-a-retained-local-tag)
shows that the tag can remain while the returned object ID is empty and the
error does not identify that partial local state.
When the recovery behavior is selected and fixed, use a controlled Git wrapper
and a real temporary repository to fail post-creation inspection. Verify both
the actual local ref and the reported recovery evidence, the original error,
and absence of a remote push. Do not rely solely on a mocked return value.
## 4. Some assertions lock in incidental internal mechanisms
[TestMarkdownCacheSharesParsedDocumentsByBlobObjectID](../../../internal/validation/markdown_test.go)
requires two parsed document pointers to be identical. A correct cache that
returns independent immutable values, or another memory strategy, would fail
without changing link validation. The separate
[shared-document link test](../../../internal/validation/markdown_check_test.go)
already checks that identical content resolves relative to each source path.
Consider removing the pointer-identity assertions or the cache-specific test
after retaining any unique parsing checks at the behavior boundary.
`TestDeliverReadsLocalBinaryOnceForVerificationAndOnceForUpload` in
`delivery_reads_linux_test.go` at audit baseline `3d7a691`
requires exactly two filesystem opens using Linux inotify. Verification before
hosted writes and correct uploaded bytes matter; the exact number of opens is
an implementation choice, and a harmless extra metadata open or a different
safe streaming approach would fail the test. Existing delivery and artifact
tests cover tampering and uploaded content. Consider removing this exact-count
test or moving a justified read-volume concern to a benchmark, while retaining
behavioral verification of artifact integrity and ordering.
The success case in [TestPublishPushesOnlyTheCapturedAnnotatedTag](../../../publish_test.go)
also asserts exactly three remote queries, including no post-success read-back.
That exact success-path count is not necessary to establish the public result,
and would reject an additional verification step even if it preserved the
contract. Consider removing that assertion while retaining the exact remote
tag, message, and unrelated-ref checks. The separately documented single
reconciliation query after a failed push remains a distinct contract.

View File

@@ -1,51 +0,0 @@
# API and interface contract audit
Reviewed baseline: `b345bc0` on `main`. The review compared the public Go
facades, CLI and profile behavior, and internal Git, process, validation,
build, and forge interfaces with their documented contracts and callers.
The existing [correctness](01-correctness.md), [concurrency](04-concurrency.md),
[resource](05-resources.md), [error-handling](06-error-handling.md), and
[security](07-security.md) audits already record related contract violations;
they remain pending and are not repeated here.
The compiled public API and dependency-boundary check passed. The finding
below was reproduced through the public API in an isolated temporary Git
repository with a controlled Go-command wrapper. No production code or
repository tests were modified.
## A partial build result can contain an artifact without its promised digest
**Relevant interfaces and code:** [BuildArtifact and BuildResult](../../../build.go),
[BuildResolved and hashArtifact](../../../internal/build/build.go), and the
build failure presentation in [the CLI](../../../cmd/releasekit/main.go).
`BuildArtifact.SHA256` is documented as the lowercase digest of the complete
file, and `BuildResult.Artifacts` describes completed outputs, including
partial success. After a compiler reports success, `BuildResolved` appends the
artifact to its result before opening and hashing the file. If opening or
reading that output fails, it returns the partial result with that artifact's
`SHA256` still empty. The public conversion forwards the entry unchanged; the
CLI also prints it among the built outputs without a digest.
This can occur if the generated file becomes unavailable or unreadable before
or during hashing, or if the filesystem reports a read failure. A caller
examining partial results cannot treat every returned artifact as carrying the
documented digest, and an unavailable path may be presented as an output. The
operation does return an error and does not claim a successful complete build;
this finding concerns the contents of its partial result.
**Evidence:** A temporary clean module and a wrapper around the real Go
command simulated a file disappearing after a successful compilation. The public
`Build` call returned an error and one artifact with `SHA256` equal to the
empty string. This controlled probe exercised the failure boundary, not a real
filesystem fault.
**Potential approaches:** Decide how callers should learn about a compiled
output whose digest could not be obtained. One option is appending an artifact
to the completed list only after hashing succeeds, while preserving earlier
completed artifacts and explaining the affected path in the error. If
reporting the unverified output is required for recovery, represent that state
explicitly and document the corresponding partial-result contract. Preserve
the failure cause and existing partial outputs; do not silently claim a digest
or delete caller-owned output. A focused regression can inject a post-build
read failure and verify the public result and CLI presentation.

View File

@@ -1,84 +0,0 @@
# Behavioral equivalence and shared abstraction audit
Reviewed baseline: `5e6db4d` on `main`. This review started with tests of
corresponding behavior and traced the implementation behind each candidate.
It considered the existing facade and package boundaries, including the
[architecture policy](../../policy/architecture.md), and retained deliberate
differences between operations. No production code or tests were modified.
The offline tests for `internal/candidate`, `internal/git`, `internal/delivery`,
and `cmd/releasekit/internal/cli` passed using the Go test cache. Focused public
tests of build source eligibility, inspection errors, and dirty tagged-source
delivery also passed.
## 1. Source-state observations are independently implemented
**Test evidence:**
[TestCheckRejectsDirtyTrackedAndUntrackedState and TestCheckRechecksHeadAndWorktreeAfterCallback](../../../internal/candidate/check_test.go)
exercise clean worktree/index and stable-HEAD requirements for candidate
checking. [TestBuildSourceEligibilitySurvivesLaterCheckoutCleanup](../../../build_test.go)
checks the same kinds of changes before and during a build, while
[TestDeliverRejectsDirtyTaggedSourceBeforeBuildOrHostedWrites](../../../delivery_test.go)
and [TestInspectTaggedSourceReportsDirtyCheckoutAndReadCommitFile](../../../internal/git/source_test.go)
exercise dirty-state observations for tagged delivery. These tests imply a
common need for accurate Git HEAD and repository-wide status observations;
they do not imply identical decisions about dirty source.
**Implementation:** [candidate.inspectLocalState](../../../internal/candidate/check.go)
runs branch, HEAD and porcelain-status commands. [build.gitCommit and cleanSource](../../../internal/build/build.go)
independently run HEAD and status commands through direct subprocesses.
[git.CaptureHead and InspectTaggedSource](../../../internal/git/source.go)
perform overlapping observations through the Git package's command boundary.
**Opportunity:** The existing Git package could own small shared commit and
status observation primitives, leaving each operation to choose when to read
them and how to interpret them. This would centralize a concrete Git
responsibility and reduce the number of paths needing parallel fixes for
inspection, error propagation, and cancellation. The
[complexity audit](03-complexity.md#2-git-source-inspection-is-repeated-across-package-boundaries)
also identifies the duplication; the tests above establish the common
behavioral evidence for considering it here.
**Tradeoffs:** Candidate checking rejects dirty state and checks the branch;
builds can succeed with dirty state but record source eligibility before and
after compilation; delivery additionally checks a specific tag. Do not merge
those policies or alter observation timing. Retain separate caller-level
tests, so a shared helper cannot make every test pass while all consumers
interpret its results incorrectly. Coordinate with the existing
[replacement-object and cancellation findings](01-correctness.md).
## 2. Build-artifact declarations are checked independently by receipts and delivery
**Test evidence:**
[TestBuildReceiptRoundTripBindsRootAndNestedModuleIdentity](../../../cmd/releasekit/internal/cli/receipt_test.go)
constructs and reloads a receipt binding a selected target to its expected
filename. [TestDeclaredBuildAssetsGenerateManifestFromBuildResult and TestDeclaredBuildAssetsRejectsArtifactDeclarationMismatch](../../../internal/delivery/assets_test.go)
exercise the corresponding build-result declarations and reject a mismatched
filename. [TestBuildAndPrebuiltDeliveryUseSameDirectoryBase](../../../cmd/releasekit/assets_test.go)
connects the two paths through a real build, receipt, and prebuilt delivery.
Together they support a shared requirement that a target's recorded artifact
identity agree with the selected build, without requiring receipt loading to
read artifact contents.
**Implementation:** Both
[validateBuildReceipt](../../../cmd/releasekit/internal/cli/receipt.go) and
[declaredBuildAssets](../../../internal/delivery/assets.go) independently
check target uniqueness, artifact counts, one artifact per target, and names
derived from targets. They already share `artifact.ExpectedBuild` and
`artifact.ValidComponent`, but each builds and checks its own target-to-name
mapping.
**Opportunity:** Consider a narrow, pure declaration-matching helper in the
existing [artifact package](../../../internal/artifact/artifact.go), accepting
target/filename metadata and reporting a mismatch without opening files. Both
paths could reuse it after converting their own types. Centralizing the shared
identity rule would reduce drift when target or filename conventions evolve.
**Tradeoffs:** The receipt additionally requires a schema, specific output
path and digest syntax, and intentionally loads without checking the files;
delivery accepts source-only results and separately stats and verifies local
content after preflight. Keep those boundary-specific rules and ordering where
they are. Avoid making `internal/artifact` depend on CLI receipt types,
`internal/build`, or forge policy. Preserve independent public and CLI tests
with concrete expected filenames and bytes rather than using the new helper to
compute every test expectation.

View File

@@ -1,59 +0,0 @@
# Dead code and obsolete functionality audit
Reviewed baseline: `083bc83` on `main`. The review considered supported public
facades and CLI commands, internal package callers, tests and benchmarks,
maintained examples, documented decisions, and build-specific uses. Public
symbols were not classified as unused merely because this repository does not
call them. No production code or tests were changed.
The local code graph and a source reference scan identified candidates, which
were checked against current source and documentation. The graph contained a
stale build-function entry and excluded `internal/build` during indexing, so
its reachability results were not treated as conclusive. The scan is a
candidate-finding aid rather than a proof of whole-program reachability.
`go vet ./...` and the compiled public API/dependency-boundary check passed
offline. No dedicated dead-code analyzer was installed.
## 1. Committed-selection identity wrapper has no callers
**Relevant code:** [FromCommittedSelection](../../../internal/releaseidentity/identity.go).
The repository contains no call or function-value reference to this exported
internal wrapper, including in tests and maintained examples. It creates a
committed-path resolver and delegates to the private `fromCommittedSelection`.
The live `FromCommitted` path calls that private function directly with the
resolver it already used to select the directory. The independently used
`ResolveCommittedSelection` serves the CLI's asset-directory selection and
must not be confused with the unused wrapper.
Because Go's `internal` import restriction prevents ordinary external consumers
from importing this package, this is not a root-facade compatibility removal.
No documented retention or future use was found. Consider removing only the
unused wrapper after confirming that no maintained in-tree integration needs
it. Preserve the private implementation and the live selection APIs; the
wrapper's absence does not imply that committed identity derivation is dead.
## 2. Local tag inspection helper is used only by its own test
**Relevant code:** [InspectLocalTag](../../../internal/git/tag.go), its only
call in [TestTagOperationsAcceptFullSlashContainingRefs](../../../internal/git/source_test.go),
and the live local guard in [candidate.Recheck](../../../internal/candidate/check.go).
The helper checks that a local ref identifies the captured annotated tag
object, but no production caller invokes it. `candidate.Recheck` independently
performs the corresponding object-ID and type checks before publication. The
test calls the helper directly and then separately inspects the tagged source;
the public publication guard is exercised through the candidate and
publication tests. The helper is in an internal package, not the supported Go
facade. No documented reason to retain this separate test-only path was found.
Consider removing the unused helper and its direct test call while retaining
the test's full-tag and source assertions and the live publication-guard
coverage. Alternatively, evaluate using the existing helper in the live guard
as part of the [shared Git inspection work](10-equivalence.md#1-source-state-observations-are-independently-implemented),
provided error behavior, guard timing, and the exact captured-object check are
preserved. Do not remove the live guard merely because the duplicate is unused.
The `CandidateTree` accessors and root-scope constructor are also used by tests
or benchmarks, but those uses exercise meaningful tree behavior; this review
does not recommend removing them solely because production uses a different
entry point.

View File

@@ -0,0 +1,67 @@
# Deferred resource work
These two proposals remain deferred after the completed audit remediation for
v0.7.0. Their assessments are complete; production limits and exclusion indexing
await a future decision supported by consumer needs. The assessment evidence
was recorded in commit `9dc87ae`.
## Forge JSON response budgets
**Status:** Deferred; local sizing assessment complete.
Forge JSON decoding can consume memory proportional to response size. Release
notes and unpaginated attachment lists have no established supported maximum.
A future proposal should establish an appropriate response envelope before
selecting byte/time budgets or configuration. Any limit must retain context
cancellation, useful error classification, and uncertain-write reconciliation:
a rejected response does not establish that a create or upload failed remotely.
The [Gitea fixtures](../../internal/gitea/backend_test.go) construct repository,
release, tag, and attachment responses. Representative encoded fixtures measured
81 bytes for repository identity, 136 with permissions, 125 for a sparse release,
144 for a populated release, 73 for a one-item attachment list, and 62 for an
upload response, including the encoder's newline. Four literal release-ID
fixtures measured 21, 28, 29, and 29 bytes without a newline. Generated URLs,
notes, and lists vary with fixture inputs.
The checked-in v0.1.0–v0.6.0 release notes measured 1,235–7,899 bytes during the
assessment. These observations describe local examples and establish no
deployment or protocol maximum. Production response caps remain deferred.
## File-size exclusion indexing
**Status:** Deferred; benchmark assessment complete.
The current [exclusion scan](../../internal/validation/file_size.go) compares
each selected file with the normalized exclusions until a match is found.
The adoption decision weighs measured speedups against allocation and
path-walking costs for consumer workloads. Preserve literal component boundaries,
module-relative normalization, shared inputs, and the first oversized-file result.
The [benchmark-only prototype](../../internal/validation/file_size_benchmark_test.go)
compares the scan with a normalized set and ancestor lookups. It covers 1,000
and 10,000 paths, 1/10/100/1,000 exclusions, shallow/deep paths, early matches,
and nonmatches. Each measured operation includes normalization and one index
construction where applicable; fixture strings are prepared outside timing.
On Linux/amd64, Go 1.26.8, Intel Core i5-7260U, 64 combinations ran with three
samples each using:
```sh
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test ./internal/validation \
-run '^$' -bench '^BenchmarkFileSizeExclusions$' -benchtime=100ms -count=3
```
Representative median times and allocations were:
| Paths / exclusions / case | Scan | Index |
| --- | --- | --- |
| 1,000 / 1 / shallow early match | 24.5 µs, 16 B, 1 allocation | 44.0 µs, 16 B, 1 allocation |
| 1,000 / 1 / deep nonmatch | 25.7 µs, 16 B, 1 allocation | 119.0 µs, 16 B, 1 allocation |
| 10,000 / 1,000 / shallow early match | 270 µs, 16,384 B, 1 allocation | 564 µs, 70,992 B, 6 allocations |
| 10,000 / 1,000 / shallow nonmatch | 227.0 ms, 16,384 B, 1 allocation | 521 µs, 70,992 B, 6 allocations |
| 10,000 / 1,000 / deep nonmatch | 233.4 ms, 16,384 B, 1 allocation | 1.36 ms, 70,992 B, 6 allocations |
The prototype was slower for small lists and early matches, and faster for
large nonmatching lists with higher allocation costs. Production matching
remains unchanged pending an adoption decision.

View File

@@ -1,648 +0,0 @@
# Audit remediation implementation plan
## Objective and baseline
Implement the accepted target in [audit.md](audit.md) from baseline `b5b3f09`.
The eleven [audit reports](audit/) supply evidence and reproduction details;
the target document owns the accepted outcomes and policy choices. This plan
owns execution order, concrete implementation choices, and completion evidence.
Follow Stage 1 through Stage 19 in order as one
end-to-end sprint. Stages separate work into units suitable for a Luna coding
agent. Intermediate stages may leave related code or tests uncompilable;
complete the dependencies before evaluating the final implementation. Run
focused checks when the affected packages are ready, and reserve the complete
verification pass for Stage 19. Proceed through the sprint without intermediate
external actions, pilot integrations, releases, or human review gates.
This sprint implements the selected repairs and internal maintenance. A11 and
A17 deliver local assessment evidence while their production changes remain
deferred. New JSON limits, exclusion indexing, public configuration, broader
concurrent-use contracts, and alternative approaches are future decisions.
The public module path, Go minimum, facade types, receipt schema, and source
publication completion point keep their current contracts.
Read [development.md](../development.md) and the applicable policies before
working. Preserve unrelated changes. Use existing fixture helpers, local Git
remotes, and local HTTP servers. Keep synthetic module-proxy tests in test-owned
module caches. All default tests remain offline and credential-independent.
## Completion tracking
Update this table with the completed work and relevant check results as the
sprint proceeds. A completed assessment in Stage 18 leaves the corresponding
production change explicitly deferred. Commit completed nonempty units under
the agent policy; an intermediate compilation dependency is recorded accurately.
| Stage | Work items | Status |
| --- | --- | --- |
| 1 | A01; publication assertion portion of A19 | Complete — regression failed before the fix and passed afterward; focused publication and reconciliation tests passed. |
| 2 | A02, A12: common Git mechanics | Complete — focused Git, candidate, identity, and public build/publication/delivery tests passed; A02 regression evidence follows in Stage 3. |
| 3 | A02: source identity regression evidence | Complete — commit, tree, and blob replacement regressions and public publication, build, and delivery cases passed. |
| 4 | A03: buffered command teardown | Complete — inherited-pipe, cancellation, healthy-command, native build, and publication recovery checks passed. |
| 5 | A03, A18: piped lifetime and queued cancellation | Complete — process and Git lifecycle tests passed under the race detector; public preparation cancellation passed. |
| 6 | A04 | Complete — Git display, endpoint change, public mismatch, CLI, publication, and Git failure regressions passed. |
| 7 | A05 | Complete — real-repository inspection failures preserve the local ref, available object ID, and Git cause without a push. |
| 8 | A06, A14 | Complete — public build and prebuilt-delivery checks and CLI receipt/partial-artifact regressions passed. |
| 9 | A16 | Complete — shared declaration, receipt, delivery, prebuilt, and source-only regressions passed. |
| 10 | A07: allocation and temporary metadata | Complete — allocation resolution, joined cleanup causes, tag evidence, and receipt installation checks passed. |
| 11 | A07: build and delivery outcomes | Complete — managed and native cleanup tests cover success, failure, cancellation, hosted completion, and CLI reporting. |
| 12 | A08 | Complete — redirect boundary, origin authorization, cycle, and delivery outcome tests passed. |
| 13 | A09 | Complete — bounded reads, size and digest mismatches, stream failures, cancellation, reconciliation, and public prebuilt delivery checks passed. |
| 14 | A10 | Complete — bounded and full capture, forwarding, error identity, concurrency, and benchmark modes passed. |
| 15 | A13 | Complete — validation ordering, scope, tree lifecycle, diagnostics, and public callback checks passed. |
| 16 | A15 | Complete — the full CLI command suite passed after workflow extraction. |
| 17 | Remaining A19, A20 | Complete — Git, identity, and Markdown tests passed; delivery benchmark ran for fresh uploads and matching retries. |
| 18 | A11, A17: assessment only | Assessment complete; production JSON limits and exclusion indexing remain deferred. Evidence follows below. |
| 19 | Integrated verification and current documentation | Complete — full offline tests, race tests, API/dependency checks, formatting, and documentation link checks passed. |
## Stage 1: Correct the owned tag push
Owners: [internal/git/tag.go](../../internal/git/tag.go) and
[publication tests](../../publish_test.go). Target: A01 and the publication
assertion portion of A19.
1. Add `--recurse-submodules=no` to `Endpoint.PushTag`. Retain its captured
object-to-exact-tag refspec, `--no-follow-tags`, scoped endpoint, and
non-forcing push behavior.
2. Extend the real local-bare-remote publication fixture with
`push.recurseSubmodules=only`. Set it after fixture setup has published
`main`. A successful public `Publish` must leave the captured annotated
object at the exact remote ref, with the intended peeled commit and note.
3. Remove the ordinary success test's assertion of exactly three queries.
Keep its exact remote object/message and unrelated-ref assertions. Retain
the documented single authoritative reconciliation after a failed push.
Evidence: the new regression fails on the baseline and passes with the push
option. Existing rejected-push, conflict, and unknown-result cases retain their
outcomes. This stage adds no successful-push read-back operation.
## Stage 2: Establish shared original-object Git observations
Owners: [internal/git](../../internal/git/source.go),
[candidate](../../internal/candidate/check.go),
[build](../../internal/build/build.go), and
[committed identity](../../internal/releaseidentity/identity.go).
Targets: the mechanics of A02 and A12.
1. Make ReleaseKit-owned Git subprocesses use `GIT_NO_REPLACE_OBJECTS=1`
through per-command environment overlays. Cover the Git command runner,
local-object reader, batch-reader startup, discovery/preparation commands,
and remaining direct candidate Git commands. Preserve local-only object
reads' lazy-fetch/transport controls and network-capable preparation.
Apply the override to owned Git children; preserve the existing environments
of application Go tests, product checks, and unrelated caller operations.
2. Reuse `git.CaptureHead` and add a small repository-wide worktree-status
observation in `internal/git`. Have candidate state inspection, build
eligibility inspection, and tagged-source inspection use these primitives.
Keep branch and tag observations explicit at their current owners.
3. Remove build's independent `gitCommit` and `cleanSource` execution paths.
Preserve its initial/final observation timing, captured commit, dirty-build
success behavior, `SourceVerified` calculation, and contextual causes.
4. Keep candidate checks requiring clean `main`, and delivery requiring the
selected tag and clean checkout. Shared observation helpers return facts;
they do not impose those caller policies or combine the observations into
a new release-state framework.
5. Leave the unused `InspectLocalTag` helper unused for removal in Stage 17.
Preserve the live candidate guard's exact captured-object/type checks.
Evidence: existing dirty-source, changed-HEAD, sparse-checkout, nested-module,
Git failure, and source-cancellation cases still express their distinct
contracts. Stage 3 supplies the replacement-object regressions before A02 is
marked complete.
## Stage 3: Prove validated and published source identity
Owners: public publication/build/delivery tests and existing Git/tree fixtures.
Target: A02, including the missing regression coverage in testing audit §1.
1. Use a real candidate and bare authoritative remote at original commit A.
Create a local replacement B with different source and note bytes. Include
a case where A fails compilation while B compiles. Materialize the replaced
checkout using ordinary fixture Git, then invoke the public release path.
2. Assert that a replacement-derived worktree cannot pass candidate validation
and publish A as validated source. Compare actual bytes or the contextual
rejection, rather than only commit IDs. Also verify that a worktree matching
original objects can release normally with unrelated replacement refs.
3. Cover original committed reads for commit, tree, and blob replacements at
the Git/identity/tree boundary. Reuse one public fixture to show this behavior
reaches publication and one existing tagged-delivery fixture to prove the
committed note remains the original note. Avoid a module/layout cross-product.
4. Retain dirty working-tree `Build` support: replacement-derived dirty bytes
may compile, but their result is ineligible as clean binary delivery source.
Keep ordinary partial build facts and inspection causes.
Evidence: original committed identity/note reads and the publication outcome
agree. Do not alter refs, disable unrelated caller configuration globally, or
add a repository-wide prohibition on replacement refs.
## Stage 4: Bound buffered subprocess I/O teardown
Owners: [internal/process/process.go](../../internal/process/process.go),
[native version execution](../../internal/build/build.go), and process tests.
Target: the buffered-command part of A03.
1. Set an internal `exec.Cmd.WaitDelay` of one second in the common command
construction path. This is an I/O teardown grace after cancellation or
observed child exit, not a runtime deadline for healthy commands. It bounds
drainage of pipes inherited by a surviving descendant while using the
standard library's child waiting and error behavior.
2. Preserve `errors.Is` cancellation/deadline identity when the governing
context is done. Preserve ordinary `*exec.ExitError` and `exec.ErrWaitDelay`
when appropriate. Retain the existing 30-second recovery context; its
cancellation may be followed by bounded teardown, rather than an unbounded
inherited-descriptor wait. Introduce no new public timing SLA or setting.
3. Route build's native version command through `process.Run`, capturing stdout
and stderr together. Preserve its current working-directory behavior,
literal `version` argument, exact expected output, and contextual failures.
Stage 2 has already moved build's Git commands to the shared boundary.
4. Add ready-child/ready-descendant fixtures for cancellation and normal child
exit with inherited descriptors. Check bounded return and error identity;
clean up the fixture's descendants explicitly in test cleanup. Keep a
generous test watchdog separate from the internal teardown grace.
Evidence: buffered Go/Git preparation can return after cancellation, healthy
long-running commands continue until their caller cancels or they finish, and
the existing source-publication recovery classifications remain unchanged.
## Stage 5: Complete piped ownership and queued-read cancellation
Owners: `PipedProcess` in [internal/process](../../internal/process/process.go)
and [BlobReader](../../internal/git/blob_reader.go). Targets: A03 and A18.
1. Keep serialized batch exchanges and the existing `sync.Once` wait ownership.
After a piped command starts, register an operation-context cancellation
callback that closes its owned stdin/stdout pipes so an exchange or normal
shutdown drain can unblock even if a descendant holds a descriptor.
2. Retain the callback's stop function and a completion signal. During `Wait`,
join the child and stop or join that callback before returning and before
reading captured stderr. On healthy completion, stop the callback before
releasing the derived context. Startup failures close acquired pipes and
release the context. Explicit `Cancel` continues leaving the parent context
unchanged.
3. Retain `BlobReader.Read`'s per-read cancellation hook and its completion
join; it may delegate pipe interruption to the piped-process owner. Keep
aborted sessions terminal, and preserve ordinary missing-object reuse.
Ensure `Close` uses the same cancellation/pipe lifetime and reaps the child.
Healthy exchanges still consume stdout before the normal wait; the one-second
grace does not become a lifetime limit on a healthy batch reader.
4. Recheck `ctx.Err()` immediately after acquiring `reader.mu`, before session
access or callback registration. A canceled queued request returns its own
context error without aborting the healthy session. Retain the mutex;
prompt cancellation while waiting for it remains outside this sprint.
5. Add controlled regressions for inherited-descriptor shutdown, cancellation
during a body read, and a canceled queued request between a successful first
read and a successful later read. Preserve double-close and parent-context
behavior. Use readiness signals and joined cleanup, not timing alone.
Evidence: process and reader lifecycle tests pass under the race detector.
Use one public preparation cancellation case to confirm caller-visible return;
the test owns any descendant left running after the direct child is reaped.
## Stage 6: Sanitize Git endpoint diagnostics
Owners: [internal/git/remote.go](../../internal/git/remote.go),
[tag operations](../../internal/git/tag.go),
[publication](../../internal/publish/publish.go), and the CLI. Target: A04.
1. Add narrow internal Git URL display helpers. Mask the entire HTTP(S)
userinfo component, covering password- and username-carried credentials.
Retain useful scheme/host/path context. Keep non-HTTP Git forms unchanged.
2. Use the helper for expected/resolved endpoints, endpoint changes, queries,
pushes, and reconciliation messages. Sanitize credential-bearing HTTP(S)
URL spans in captured Git error diagnostics before embedding them. If a
detected userinfo URL cannot be parsed safely for display, replace that span
with a redacted-URL marker. Preserve wrapped causes and error classification.
Where the cause's own text contains credentials, use a private error wrapper
whose `Error` renders sanitized text and whose `Unwrap` retains the cause;
embedding the raw cause with `%w` would expose its text again.
This addresses URL credentials in owned diagnostics, not arbitrary secrets
emitted by application checks or unrelated transport programs.
3. Render the CLI's `endpoint=` field through the same display logic. Keep
`PublicationResult.Endpoint`, captured transport URLs, URL rewrites, and
`ExpectedPushURL` comparisons raw and exact. Clarify the public field's
sensitivity in GoDoc without adding a new public accessor or field.
4. Cover password and username tokens, encoded userinfo, embedded Git diagnostic
URLs, malformed display inputs, and ordinary SSH destinations. A public
mismatch fixture must preserve exact mismatch detection while both the
library error and CLI output hide placeholder credentials. Also exercise
a later query/push failure and preserve the original cause.
Evidence: owned diagnostics are safe to display, while endpoint identity and
transport behavior retain their selected contracts. Use placeholder credentials
and local fixtures; live Git or forge credentials are unnecessary.
## Stage 7: Preserve partial local tag evidence
Owners: [CreateAnnotatedTag](../../internal/git/tag.go),
[publish.Publish](../../internal/publish/publish.go), and publication tests.
Target: A05.
1. Retain the annotated tag object ID once its existing inspections establish
that evidence. Return it with a later verification error rather than
returning an empty string on every error.
2. Assign any returned tag object to the publication result before handling the
error. Keep phase `tag_creation` and state `not_published` for these failures.
3. After successful tag creation followed by failed inspection, identify
`refs/tags/<tag>` in the error and explain that the local tag may remain.
When no ID was obtainable, retain the ref-level evidence and empty object
field. Preserve the original cause and failed verification; perform no
automatic local ref deletion, retagging, independent retry, or push.
4. Add public regressions using real repositories and a command wrapper that
fails the first post-creation type inspection and a later inspection after
the ID is known. Inspect the actual local ref independently and confirm
retained evidence, original error, and absence of a remote push.
Evidence: the caller can distinguish possible retained local work from a
verified tag object without a new public progress type. Update the relevant
public comment and recovery guidance to reflect unavailable evidence accurately.
## Stage 8: Normalize internal build results and completed artifacts
Owners: [internal/build/build.go](../../internal/build/build.go),
[facade conversion](../../build.go), and public build/CLI tests.
Targets: A06 and A14.
1. Remove internal `Result.MainPackage`, `BinaryName`, and `Targets`; use
normalized `Result.Settings` throughout compilation, native smoke selection,
and conversions. Keep public `BuildResult` fields and receipt fields intact.
Preserve early-failure normalization and result copying.
2. Build and hash each artifact in a local value before appending it to the
completed artifact list. A hash failure names that path, preserves its cause,
retains earlier completed artifacts, and leaves generated files in place.
Later smoke or source-inspection failures still return all already completed
artifacts. Introduce no compiled-but-unverified public result state.
3. Extend the public post-compilation read-failure fixture to include an earlier
successful target. Assert complete digests on the retained prefix, absence
of an incomplete entry, accurate CLI presentation, and preserved source/
settings facts. Retain no-target, dirty-build, duplicate-target normalization,
partial-target-failure, and version-injection coverage.
Evidence: public artifacts satisfy the documented digest contract, and prebuilt
delivery still accepts the same normalized public results.
## Stage 9: Share pure artifact declaration matching
Owners: [internal/artifact](../../internal/artifact/artifact.go),
[receipt validation](../../cmd/releasekit/internal/cli/receipt.go), and
[delivery declarations](../../internal/delivery/assets.go). Target: A16.
1. Introduce a pure helper accepting selected targets, expected-name inputs,
and recorded target/filename metadata in artifact-owned internal types.
Reuse `ExpectedBuild` and existing component rules to check counts, target
uniqueness, one artifact per selected target, and expected names.
2. Adapt both callers through explicit conversions. Keep receipt schema,
absolute-path, identity, digest-syntax, and nonempty-target rules in the CLI.
Keep delivery's empty/source-only behavior, file stats, manifest generation,
and byte verification in their current owners and ordering.
3. Preserve current acceptance rules; add no Go target allowlist or content
access during receipt loading. Keep `internal/artifact` independent of build,
CLI, delivery, and forge packages.
4. Retain independent concrete expected filenames in receipt, delivery, and
public receipt-to-delivery tests. Reuse mismatch cases rather than computing
every expected value with the helper under test.
Evidence: schema and content validation remain distinct, with one owner for
their shared declaration rule and unchanged source-only behavior.
## Stage 10: Preserve allocation paths and temporary metadata cleanup
Owners: [internal/tempdir](../../internal/tempdir/tempdir.go),
[tag message files](../../internal/git/tag.go), and
[receipt files](../../cmd/releasekit/internal/cli/receipt.go). Target: A07 part 1.
1. In both `Outside` allocation branches, keep the original `MkdirTemp` path
until `Abs`/`EvalSymlinks` succeed. Use that original path for error-path
removal. Retain the resolution error and report any failed removal with
its path. Preserve outside-worktree selection and fallback behavior.
2. Capture deferred removal errors for owned tag-message and receipt temporary
files and join them with primary errors. A temporary path absent after a
successful rename is normal; ignore `os.ErrNotExist` for that removal.
Keep successful receipt installation and caller output paths intact.
3. Preserve any verified local tag ID through a later metadata cleanup error,
as established in Stage 7. Cleanup failure before the push remains a local
publication failure; no extra remote action follows it.
4. Use a narrow private, per-operation removal/resolution test seam where real
filesystem failures are otherwise nondeterministic. Keep the seam behind
internal wrappers, with standard functions in production and no mutable
global hooks or public filesystem abstraction. Test path retention and joined
causes, including the renamed-receipt absent-path case.
Evidence: failed resolution never loses the owned cleanup path, and routine
successful receipt writes do not acquire a spurious cleanup error.
## Stage 11: Report managed build cleanup with accurate outcomes
Owners: [candidate build callback](../../internal/build/candidate.go),
[native smoke build](../../internal/build/build.go),
[managed delivery](../../internal/delivery/delivery.go), and CLI delivery output.
Target: A07 part 2.
1. Use named returns or equivalent explicit finalization to join failed owned
directory removal with a primary build/delivery error. Include the cleanup
path and preserve completed artifacts, source facts, and remote observations.
2. If remote delivery completed, return its `Complete` state and asset lists
together with the cleanup error. The public facade forwards both. The CLI
returns operational status 1, explicitly reports remote completion plus local
cleanup failure, and gives no instruction to redo completed hosted delivery.
Earlier partial/conflict/unknown outcomes retain their original classification.
3. Keep managed temporary output outside the checkout. Caller-owned prebuilt
artifacts and normal caller-selected build directories are not removed by
this finalizer. Candidate build cleanup occurs before tagging as today.
4. Test success-plus-cleanup-failure, primary-error-plus-cleanup-failure,
cancellation, and confirmed hosted completion with cleanup failure. Use a
private per-operation remover parameter at the internal operation boundary
where needed for deterministic failures; keep production entry points using
`os.RemoveAll`. Test the outcome, not only a standalone join helper.
Evidence: errors identify leaked paths without misreporting remote state or
discarding existing causes. Update delivery/CLI guidance for `Complete` plus
a local operational error and preserve ordinary cleanup success cases.
## Stage 12: Bound manual asset redirects
Owners: [internal/gitea/assets.go](../../internal/gitea/assets.go) and adapter/
delivery tests. Target: A08.
1. Count followed redirect hops in `openAsset`. Allow ten hops: the initial
request plus at most ten follow-up requests. After ten followed redirects,
a further redirect response is closed and rejected before requesting its
target. A successful response after the tenth hop remains accepted.
2. Keep fragment normalization, exact-cycle handling, invalid-userinfo rejection,
and per-origin token checks. Close every intermediate/rejected body. Return
the existing unverifiable-content failure classification with useful context.
Add no public override or automatic operation timeout.
3. Test zero hops, exactly ten, and an eleventh attempted hop with distinct
URLs. Retain same-origin, cross-origin, return-to-origin, and exact-cycle
checks. Use local servers and request counters to verify the boundary.
4. Preserve shared outcome mapping: read-only verification failure leaves
`NotAttempted`, or `Partial` when this invocation created the page; ambiguity
after an attempted asset write still follows existing reconciliation rules.
Evidence: the selected hop count is unambiguous and authorization never follows
the token to another origin. Document the limit in the Gitea integration owner.
## Stage 13: Bound remote content comparison by the declaration
Owners: [internal/delivery/remote_assets.go](../../internal/delivery/remote_assets.go)
and delivery/artifact fixtures. Target: A09.
1. Pass expected local size into the digest comparison. Read at most that size
through a limited reader, then probe one additional byte. Keep the extra-byte
fact separate from an `int64` total so `MaxInt64 + 1` never overflows.
2. Distinguish definite size mismatch from inspection failure. Short or excess
content becomes `AssetConflicting`; equal-length content still requires
digest equality. A non-EOF read error, close error, or context error remains
unverifiable with its cause retained. Close the stream on every path.
3. Apply the same verification during upload reconciliation. A mismatch there
conflicts; an unresolved read after a possibly successful write remains
unknown under the existing policy. Keep local hashing before hosted writes,
additive uploads, and asset observation categories unchanged.
4. Test empty, shorter, equal/matching, equal/different, and oversized streams,
plus a read returning data with an error, close failure, and cancellation.
Count bytes in a controlled stream to prove the declaration-derived bound.
Include a public prebuilt-delivery case and retain manifest comparison.
Evidence: remote metadata size/content length is not used as proof of matching
bytes, and legitimate artifact size has no new global cap.
## Stage 14: Bound retained status-only Go diagnostics
Owners: [internal/gotool/gotool.go](../../internal/gotool/gotool.go), its tests,
and [output benchmarks](../../internal/gotool/performance_test.go). Target: A10.
1. Give combined capture an explicit full-output mode and a status-diagnostics
mode. Full mode retains `Tool.Output` bytes and the existing ownership
transfer. Status mode retains the last 1,048,576 bytes and whether earlier
bytes were omitted. Use a lazy bounded circular tail so repeated small writes
copy in proportion to incoming data rather than shifting a 1 MiB buffer on
every write. Keep synchronization around capture and forwarding.
2. Use status mode for `Tool.Run` and dependency preparation. Preserve complete
forwarding to existing optional writers, short-write/error handling, quiet
behavior without a writer, and the separate preparation Go environment.
Keep configuration-discovery JSON and `Tool.Output` complete. Add no public
output field or diagnostic-retention setting.
3. On failed status commands, include a clear truncation notice only when
needed, followed by the retained tail, while wrapping original process and
forwarding errors. Successful status calls release retained diagnostics.
4. Adapt existing output tests and benchmarks to exercise both modes. Test
below/exact/above budget, a single oversized write, wrapped small writes,
concurrent stdout/stderr, forwarded bytes, and error identity. Benchmark
modest output and 8 MiB/32 KiB-chunk output with allocated bytes reported.
Evidence: the retained status tail is bounded, full-output consumers keep all
bytes, and forwarded output remains complete. Update the internal Go-output
guide and its benchmark description to the implemented modes.
## Stage 15: Separate validation sequence and tree lifetime
Owners: [internal/validation/validation.go](../../internal/validation/validation.go)
and validation lifecycle/diagnostic tests. Target: A13.
1. Extract private Go-command and committed-tree execution functions. Keep the
visible order: tests, selected full vet, selected compilation, forbidden
files, formatting, workspace files, local replacements, file size, Markdown.
Shared-file selection alone still validates explicit file presence.
2. Let the committed-tree sequence own its close and the last content-reading
check. Preserve tree-load failure precedence, first check failure, and the
rule that reader-close failure replaces success only after a content read.
Keep context and underlying diagnostic causes through the public facade.
3. Preserve nil versus empty shared files and optional check selections,
operation-local Go settings reuse, and single execution of additional checks
and build callbacks by their existing owners. Use private functions and
explicit check blocks; retain current package and public interfaces.
Evidence: existing combined-check order, scope, Markdown, and lifecycle tests
remain meaningful. Add a focused case only if existing tests leave one of the
preserved precedence or metadata-only close behaviors unprotected.
## Stage 16: Extract private CLI workflows
Owners: [cmd/releasekit/main.go](../../cmd/releasekit/main.go) and CLI tests.
Target: A15.
1. Extract named private handlers for check/publication and for build. Keep
argument parsing, containing-repository resolution, version dispatch, and
command routing in `run`. Existing delivery handlers remain the local model.
2. Preserve profile loading, captured-candidate environment, expected module
and endpoint mapping, receipt invalidation before build-error reporting,
receipt verification, partial-artifact output, and all exit classifications.
Carry Stage 6's safe endpoint display and Stage 11's cleanup reporting into
the handlers without adding command flags or public interfaces.
3. Keep the CLI calling the public facade for release operations. Existing
internal path/configuration helpers retain their current role. Reuse the
command tests for profile errors, successful operations, known failures,
and unknown publication/delivery outcomes.
Evidence: the dispatcher is small while command behavior and failure evidence
match the selected contracts. No new CLI framework is needed.
## Stage 17: Remove incidental assertions and unused helpers
Owners: Markdown and Linux delivery-read tests, public publication tests,
[release identity](../../internal/releaseidentity/identity.go), and
[local tag helpers](../../internal/git/tag.go). Targets: A19 and A20.
1. Remove Markdown cache pointer-identity assertions; retain unique parsing
checks and identical-blob source-relative link behavior. Existing behavioral
tests remain the protection for correctness.
2. Remove `TestDeliverReadsLocalBinaryOnceForVerificationAndOnceForUpload`
as an exact inotify-open-count contract. Retain independent tampering,
pre-write verification, and uploaded-byte checks. Use the existing delivery
benchmark to observe read/verification cost; no replacement exact-open
assertion is required. Stage 1 handled successful-path query counts.
3. Remove only the unused `FromCommittedSelection` wrapper, keeping the private
implementation and live `FromCommitted`/`ResolveCommittedSelection` paths.
4. Remove unused `InspectLocalTag` and its direct test call, preserving the
test's other full-tag/source assertions and the live candidate guard.
Retain tree accessors and constructors used meaningfully by tests/benchmarks.
Evidence: source references show no remaining use of removed symbols; caller
behavior and public exports remain covered. Avoid tests that merely mirror
the deleted helpers or assert a new incidental call count.
## Stage 18: Record the deferred resource assessments
Targets: A11 and A17. Deliver evidence in their status notes here; accepted
production behavior remains as defined in [audit.md](audit.md).
1. For A11, inventory local repository/release/attachment JSON fixtures and
checked-in release-note sizes. Record measured byte sizes, the kinds of
responses represented, and missing evidence about deployment/protocol maxima.
Local fixtures establish observed sizes, not a supported maximum. Record
that JSON size/time caps and new configuration remain deferred; preserve
current cancellation and ambiguous-write handling. Live service queries
or an external sizing decision are not prerequisites to finish this sprint.
2. For A17, add `BenchmarkFileSizeExclusions` in a validation benchmark file.
Compare the current scan with a benchmark-only normalized set/ancestor
prototype for 1,000 and 10,000 paths; 1, 10, 100, and 1,000 exclusions; and
representative shallow/deep paths with early matches and nonmatches.
Count one index construction per simulated operation, report allocations,
and prepare unrelated fixture strings outside timing. Preserve literal
semantics in the comparison; do not add the prototype to production code.
3. Run the benchmark with a bounded measurement interval, for example:
```sh
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test ./internal/validation \
-run '^$' -bench '^BenchmarkFileSizeExclusions$' -benchtime=100ms -count=3
```
4. Record toolchain/platform, command, timings/allocations, and representative
common/large-list tradeoffs. Keep production exclusion matching unchanged
in this sprint; the assessment supplies evidence for a later adoption
decision. Mark both items as assessment completed / production change
deferred, rather than claiming either resource policy is implemented.
Evidence: both deferred items have a concrete disposition and useful evidence
without inventing unapproved protocol limits or performance thresholds.
### Assessment evidence
**A11 — observed local sizes, not limits.** The Gitea tests construct repository,
release, tag, and attachment responses in code; the public delivery fixture also
constructs release and attachment lists with variable URLs, note bodies, and
asset counts. Representative encoded fixture responses, including the JSON
encoder's trailing newline, measured 81 bytes for repository identity, 136
bytes with permissions, 125 bytes for a sparse release response, 144 bytes for
a populated release response, 73 bytes for a one-item attachment list, and 62
bytes for an attachment upload response. The four literal release-ID fixtures
in `internal/gitea/backend_test.go` are 21, 28, 29, and 29 bytes without a
newline. Empty, malformed, and missing-field responses are also represented;
generated list and note lengths vary with the fixture inputs.
The eight checked-in release notes measured: `v0.1.0` 3,400 bytes, `v0.2.0`
6,086, `v0.3.0` 6,006, `v0.4.0` 7,899, `v0.4.1` 1,235, `v0.4.2` 1,826,
`v0.5.0` 3,179, and `v0.6.0` 7,297. These local examples do not establish
deployment or protocol maxima. JSON size/time caps and new configuration remain
deferred; existing cancellation and ambiguous-write handling remain in force.
**A17 — benchmark-only index.** On Linux/amd64, Go 1.26.8, Intel Core
i5-7260U, the following command ran 64 combinations, three samples each:
```sh
PATH=/home/eric/sdk/go1.26.8/bin:$PATH TMPDIR=/var/tmp \
GOCACHE=/tmp/releasekit-go-cache GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off \
go test ./internal/validation -run '^$' -bench '^BenchmarkFileSizeExclusions$' \
-benchtime=100ms -count=3
```
Each operation normalizes exclusions and, for the prototype, builds one index.
Paths and fixture strings are prepared outside the timed interval. Median
times and allocations for representative cases were:
| Paths / exclusions / case | Scan | Index |
| --- | --- | --- |
| 1,000 / 1 / shallow early match | 24.5 µs, 16 B, 1 allocation | 44.0 µs, 16 B, 1 allocation |
| 1,000 / 1 / deep nonmatch | 25.7 µs, 16 B, 1 allocation | 119.0 µs, 16 B, 1 allocation |
| 10,000 / 1,000 / shallow early match | 270 µs, 16,384 B, 1 allocation | 564 µs, 70,992 B, 6 allocations |
| 10,000 / 1,000 / shallow nonmatch | 227.0 ms, 16,384 B, 1 allocation | 521 µs, 70,992 B, 6 allocations |
| 10,000 / 1,000 / deep nonmatch | 233.4 ms, 16,384 B, 1 allocation | 1.36 ms, 70,992 B, 6 allocations |
The prototype is slower for small lists and early matches but substantially
faster for the large nonmatching list, at a higher allocation cost. Production
matching remains unchanged; these observations support a later adoption decision.
## Stage 19: Verify the complete target and update current documentation
1. Inspect the combined implementation against all A01–A20 outcomes and the
stage table. Confirm the fixes interact correctly: original-object reads
use the shared canceled-process boundary; partial tag evidence survives
cleanup errors; completed artifacts feed shared declarations; content limits
preserve preflight/write order and reconciliation outcomes; CLI output
presents causes and reached state accurately.
2. Complete canonical documentation updates in GoDoc, `docs/cli.md`, consumer
publication/build/delivery guides, `docs/integrations/gitea.md`, and relevant
internal guides. Document the accepted redirect and diagnostic policies,
raw endpoint sensitivity, and completed hosted state with cleanup error.
Keep implementation mechanics out of user flows. Preserve public facade
shape, receipt schema, source-only release behavior, and the Go baseline.
3. Format changed Go files and run from the repository root:
```sh
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test ./...
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go test -race ./...
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go run ./internal/apicheck
```
Run focused regressions first as needed while completing their owners.
Reuse successful checks for unchanged content. Full-suite green results
complement, rather than replace, the targeted failure evidence above.
4. Check local documentation links/fragments and Markdown whitespace. Review
the diff for accidental public exports, stronger target/path restrictions,
new global deadlines, secret values, unrelated changes, and altered caller
ownership. Resolve failures within the selected scope before finishing.
5. Update this plan's stage statuses and record final verification. Keep A11
caps and A17 indexing explicitly deferred, with Stage 18 evidence. Retain
the source audit reports for review and traceability. Commit completed
nonempty work with plain English messages and report the result, remaining
deferrals, and any actual limitations. Release preparation remains governed
by the maintainer runbook.
Completion means the selected repairs and maintenance are implemented, their
behavioral evidence passes, current contracts are documented, and the two
accepted deferrals have assessment records. Intermediate stages introduce no
extra compilation, review, or deployment gates.
### Integrated verification
Reviewed the combined A01–A20 outcomes and their cross-package interactions.
The selected repairs and maintenance are implemented; A11 JSON caps and A17
production exclusion indexing remain deferred with the assessment evidence above.
The public facade shape and receipt schema remain unchanged. Current consumer,
CLI, integration, internal, and Go documentation reflects the selected
contracts and the source audit reports remain available.
On Linux/amd64 with Go 1.26.8, the following root commands passed with
`GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off` and a temporary build
cache. The test commands used `/var/tmp` for fixtures because the restricted
environment's `/tmp/.git` marker interfered with standalone Git discovery:
- `go test ./...`
- `go test -race ./...`
- `go run ./internal/apicheck`
All tracked Go files passed `gofmt` inspection. Local links and Markdown
fragments across 50 documentation files resolved; the policy's literal
`COMMIT_URL` template placeholder was intentionally excluded. Markdown and
combined changes passed `git diff --check`, and the internal guide's referenced
tests were checked against current test declarations.

View File

@@ -1,231 +0,0 @@
# Implementation review remediation
Reviewed `2f95753`, including the implementation commits after `a1dc705`,
against the accepted [audit target](audit.md) and completed
[implementation plan](implementation.md). A follow-up integration audit reviewed
`c8fa165`, whose production code is identical, and added R02 and R03. Findings
and completion records below track the remediation. Production code and existing
tests were unchanged during both reviews.
## R01. Git diagnostic redaction misses apostrophes in credentials
**Priority:** P2. **Status:** Complete. This addressed a gap in A04 and
[Stage 6](implementation.md#stage-6-sanitize-git-endpoint-diagnostics).
**Completion:** Diagnostic matching recognizes complete userinfo before
surrounding quotation, preserving error context and wrapped causes. Native Git,
literal/encoded apostrophe, malformed userinfo, and wrapped-error regressions
pass; the new regressions failed before the fix. The complete `internal/git`
suite and public/CLI endpoint-mismatch redaction tests passed offline.
**Relevant code:** `httpURLSpan` and `displayDiagnostic` in
[internal/git/display.go](../../internal/git/display.go), lines 9 and 32–39;
`pushURLs` and `run` in
[internal/git/remote.go](../../internal/git/remote.go), lines 209–252.
**Circumstances and actual behavior:** The diagnostic URL matcher treats every
apostrophe as the end of a URL. An apostrophe is also accepted within URL
userinfo. For `https://user:secret'part@example.test/repo`, the matcher passes
only `https://user:secret` to `DisplayURL`. That truncated span has no `@`, so
it is returned unchanged, leaving the entire credential in the diagnostic.
Direct rendering of the complete URL correctly masks it; sanitizing surrounding
Git text takes the defective path.
A local reproduction used a real temporary Git repository and passed the URL
above as `Resolve`'s remote name, representing a URL mistakenly configured in
the remote-name field. Native `git remote get-url --push --all` rejects that
name and includes it in its error. The returned ReleaseKit error was:
```text
resolve push URL for remote "https://redacted@example.test/repo": run "git": exit status 2: error: No such remote 'https://user:secret'part@example.test/repo'
```
The values above are placeholders. This reproduction makes no network request.
A separate controlled Git failure reproduced the same leak through `PushTag`.
The existing tests cover simple and percent-encoded credentials but omit this
literal punctuation case.
**Expected behavior and impact:** Owned diagnostics mask HTTP(S) userinfo while
preserving the underlying Git error identity. The observed failure can expose
a password to callers, terminal output, or logs even though the outer endpoint
field is redacted. The accepted credential-confidentiality repair is incomplete.
**Recommended remediation:** Recognize the authority and userinfo boundaries
before treating surrounding quotation as a URL delimiter. Sanitize the complete
credential span while retaining useful host/path and surrounding error context.
Keep malformed userinfo handling and `displayError.Unwrap`'s cause preservation.
Add a regression using native Git's local remote-name rejection, plus coverage
for the wrapped-error rendering path and literal versus encoded apostrophes.
Preserve raw transport URLs, exact comparisons, and the raw public endpoint
result. This repair requires no new credential restrictions or public API.
## R02. Original-object Git overlays retain the caller's PWD
**Priority:** P2. **Status:** Complete. This addressed the interaction of A02/A12
with the existing process environment helper and native Git hooks.
**Completion:** Child overlays now start from `Cmd.Environ()` after setting
`Dir`. Directory-derived `PWD`, explicit overrides/removals, and parent-state
preservation have process regressions; a native Go pre-push hook verifies public
publication from another directory. Both new regressions failed before the fix
and pass afterward. Process, Go-tool, and Git suites passed under `-race`;
public hook and original-object source/build eligibility regressions passed.
**Relevant code:** `run` in
[internal/git/remote.go](../../internal/git/remote.go), lines 233–240;
`newCommand` and `environmentEntries` in
[internal/process/process.go](../../internal/process/process.go), lines 146–184.
**Circumstances and actual behavior:** A caller invokes `Publish` for a checkout
other than its current directory, or invokes the CLI from a selected module
subdirectory. The new `GIT_NO_REPLACE_OBJECTS` overlay makes Git's command
environment explicit. The existing environment helper copies `os.Environ()`
before applying that overlay, retaining the parent process's `PWD`. The local
pre-push hook executes in the selected repository, while `PWD` still names the
caller's directory. Previously, this Git runner left `Cmd.Env` nil, allowing
Go to derive the child `PWD` from `Cmd.Dir` on POSIX systems.
An uncached public `Publish` probe used a real local bare remote and a Python
pre-push hook that compares `PWD` with its actual working directory. The hook
rejected the push, leaving publication `not_published` and the local tag retained.
An otherwise identical temporary comparison overlay, removing only the Git
runner's newly added environment assignment, published successfully. The
comparison isolates environment construction as the regression's cause.
**Expected behavior and impact:** Selecting another checkout preserves the
existing working-directory behavior of Git collaborators while adding the
original-object setting. Hooks that locate repository files through `PWD` can
now inspect the wrong checkout or reject a legitimate publication. The observed
failure is attributable to the new Git overlay exposing the older environment
helper's directory handling.
**Recommended remediation:** Build environment overlays from `command.Environ()`
after assigning `command.Dir`, then apply the requested removals and overrides.
This retains Go's directory-aware inherited environment and still honors an
explicit `PWD` override or removal. Keep `GIT_NO_REPLACE_OBJECTS=1` and the
existing local-object transport controls. Preserve process-global state. Add
focused process coverage for `Dir` combined with an unrelated overlay and a
public publication regression using a local hook from another caller directory.
## R03. Checkout preparation reintroduces credentials after Git redaction
**Priority:** P2. **Status:** Complete. This addressed an A04 integration gap in the
existing preparation workflow, separate from R01's URL-span parsing defect.
**Completion:** Fetch-failure errors render the remote with `DisplayURL`, while
the fetch retains its original URL and wrapped Git cause. Public preparation
and CLI regressions failed before the fix and now pass with local HTTP servers;
the public test also verifies authentication with the configured credentials.
The remaining owned Git URL renderers were reviewed, and the CLI guide describes
the implemented diagnostic behavior. Combined verification below passed.
**Relevant code:** `PrepareTaggedCheckout` in
[internal/git/prepare.go](../../internal/git/prepare.go), lines 18–20; the public
[preparation facade](../../preparation.go); and error presentation in
`runCIDelivery` in [cmd/releasekit/main.go](../../cmd/releasekit/main.go).
**Circumstances and actual behavior:** `TaggedCheckoutRequest.Remote` supplies
an HTTP(S) repository URL with userinfo, and fetching its tag fails. The shared
Git runner sanitizes its captured diagnostic, but `PrepareTaggedCheckout`
formats the original `remote` directly into its outer error. The facade returns
that error unchanged; `ci-deliver` prints it directly.
An uncached public preparation probe used placeholder credentials and a local
HTTP server returning 503. The resulting error began:
```text
fetch tag v1.2.3 from remote http://review-user:review-password@127.0.0.1:PORT/repo.git: run "git": exit status 128
```
Git's own diagnostic in that reproduction omitted userinfo. The enclosing
ReleaseKit error exposed it. Ordinary username/password credentials reproduce
this issue.
**Expected behavior and impact:** The preparation workflow uses the same safe
URL rendering as publication. As implemented, failed CI preparation or direct
facade use can expose credentials to terminal output, callers, and logs despite
the shared runner's new redaction.
**Recommended remediation:** Render the remote through `DisplayURL` in the
preparation error and check the remaining owned URL-formatting boundaries for
the same bypass. Preserve the actual fetch URL and wrapped Git cause. Add a
public preparation failure regression with a local server and placeholder
credentials, and cover the CLI error path through its existing presentation
tests. Credential-bearing transport URLs keep their current supported behavior.
## Review coverage and verification
The review covered all A01–A20 outcomes and the changed production code, tests,
and current documentation. The implementation retains the root orchestration
facade and lightweight `buildinfo` facade. Internal Git/process operations own
source observations and subprocess lifetime; validation owns its Go and tree
sequences; build settings have one internal representation; artifact declaration
matching is shared; neutral delivery policy remains separate from Gitea protocol
operations; and private CLI handlers own presentation.
R01–R03 record findings and remediation status from the initial review and
follow-up integration audit. Package ownership and public facade shape match
the target; the findings concern diagnostic and subprocess-environment behavior
across existing boundaries. The review identified no inadvertently strengthened public
contract. The selected redirect and diagnostic limits match the accepted scope.
JSON response caps and production exclusion indexing remain explicitly deferred
with assessment evidence, as planned.
During the initial review on Linux/amd64 with Go 1.26.8, these checks passed with
`GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off`:
- `go test ./...`
- `go test -race ./...`
- `go vet ./...`
- `go run ./internal/apicheck`
Some test results were reused from Go's cache. The suite used `/var/tmp` for
temporary fixtures because an environment-provided `/tmp/.git` marker affects
standalone repository discovery. The R01 probes ran uncached through a temporary
Go test overlay and failed their credential-absence assertions on the reviewed
implementation; no probe test was installed in the checkout. Those failures
substantiate R01 and are separate from the passing committed suite.
### Follow-up integration evidence
The follow-up audit traced original-object observations through candidate,
build, checkout preparation, and publication; subprocess cancellation through
batch readers and committed-tree validation; completed build facts through CLI
receipts and prebuilt delivery; cleanup errors through publication/delivery
outcomes and CLI statuses; and redirect/content limits through read-only asset
inspection and attempted-write reconciliation. It also checked status-only
output capture against full-output consumers and existing forwarding errors.
Focused committed tests ran uncached with the same offline Go settings. Public
and CLI cases covered exact publication, rejected/lost-acknowledgment recovery,
partial tag evidence, replacement-derived build eligibility, original-note
delivery, managed build cleanup/cancellation, receipt-to-prebuilt delivery,
CI preflight ordering, and completed-delivery cleanup reporting. Selected tests
in `internal/process`, `internal/git`, `internal/gotool`, `internal/delivery`,
and `internal/validation` passed under `-race`, including queued cancellation,
inherited-pipe cancellation, forwarding errors, joined cleanup outcomes,
bounded asset reads, upload reconciliation, and tree-reader cleanup precedence.
R02 and R03 were reproduced independently with temporary Go test overlays.
Their assertions failed against the reviewed implementation; R02's comparison
using the earlier Git environment behavior passed. Both audits left production
code and existing tests unchanged. The full-suite, static, and compiled
API/dependency results above record the audit baseline; completion notes record
verification of the subsequent repairs.
## Completed remediation verification
All three findings are complete. Each fix includes regressions that reproduced
the defect before the production change, and each unit has its own commit with
the corresponding completion marker.
On Linux/amd64 with Go 1.26.8, combined verification passed with
`GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off`, the prepared build cache, and
`TMPDIR=/var/tmp` for test fixtures:
- `go test ./...`
- `go test -race ./...`
- `go run ./internal/apicheck`
Changed Go files passed formatting inspection. Local links and fragments in
this document and the CLI guide resolved, and the diff passed whitespace checks.