Prepare v0.8.0 notes and retire completed roadmaps
This commit is contained in:
@@ -1,594 +0,0 @@
|
||||
# Publication reconciliation implementation plan
|
||||
|
||||
Status: complete.
|
||||
|
||||
Implement the target and resolved decisions in the
|
||||
[feature roadmap](publication-reconciliation.md). The original
|
||||
[PromptKit request](promptkit-feature-request.md) supplies consumer context.
|
||||
This document owns implementation sequence and the concrete interface choices
|
||||
needed for that work. The feature roadmap already separates capability
|
||||
requirements from staged implementation and remains the owner of intent.
|
||||
|
||||
## Sprint instructions and baseline
|
||||
|
||||
Complete Stage 1 through Stage 8 in order in one end-to-end sprint. Intermediate
|
||||
stages may leave dependent code incomplete. Continue through the stages without
|
||||
pilot deployments, intermediate releases, external actions, or human review
|
||||
gates. Follow the repository's agent and testing policies, including its commit
|
||||
discipline. Mark completed stages in this document as work lands; retain the
|
||||
remaining instructions until the sprint finishes.
|
||||
|
||||
Stage verification sections assign coverage to implement; run those checks as
|
||||
their dependencies become available and continue through temporary compilation
|
||||
gaps to the assembled validation in Stage 8.
|
||||
|
||||
Start with [development guidance](../development.md) and its applicable policy
|
||||
reading guide. Relevant implementation locations are:
|
||||
|
||||
| Location | Current responsibility |
|
||||
| --- | --- |
|
||||
| `publish.go`, `check.go` | Public publication result, publication facade, and shared release request. |
|
||||
| `internal/publish/publish.go` | Publication orchestration and immediate `reconcilePush` recovery. |
|
||||
| `internal/git/remote.go`, `internal/git/tag.go` | Scoped endpoints, transport configuration, URL checks, exact refs, and annotated tags. |
|
||||
| `internal/git/display.go`, `internal/process` | Credential-safe Git diagnostics and cancellable subprocesses. |
|
||||
| `cmd/releasekit/main.go` | Command dispatch, repository discovery, publication output, and exit codes. |
|
||||
| `cmd/releasekit/internal/cli/config.go` | Argument parsing and profile loading. |
|
||||
| `publish_test.go`, `cmd/releasekit/main_test.go`, `internal/git/remote_test.go` | Existing publication, recovery, CLI, and transport fixtures. |
|
||||
| `internal/apicheck` | Compiled facade ownership and dependency checks. |
|
||||
|
||||
Keep source publication's successful-push completion point and existing candidate
|
||||
checks. Later reconciliation and discovery are optional recovery operations.
|
||||
They observe evidence and configuration, with corrective actions remaining
|
||||
explicit. Hosted delivery retains its separate workflow.
|
||||
|
||||
Use the existing root facade and internal packages. Put shared recovery and
|
||||
record mechanics in `internal/publish`, Git mechanics in `internal/git`, and
|
||||
presentation in the CLI. Public types belong to the root facade; internal
|
||||
packages depend on internal types. Preserve the small `buildinfo` boundary.
|
||||
|
||||
## Stage 1: Extract the shared observer and classification
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** Immediate failed-push recovery and a later observation can share
|
||||
classification without sharing publication prerequisites.
|
||||
|
||||
1. Add `internal/publish/reconcile.go` with internal evidence, observation, and
|
||||
recovery-error types corresponding to the public shapes specified in Stage 3.
|
||||
Observation returns its state and available remote object/peeled-commit IDs.
|
||||
Keep the original evidence separate from the observation.
|
||||
2. Add an internal Git constructor for observing a captured endpoint. Accept
|
||||
repository context, the exact endpoint string, and an optional original remote
|
||||
name. Create a fresh command-scoped remote using the recorded URL for both
|
||||
read and push URLs. Read the named remote's current supported transport
|
||||
settings using the existing `transportConfig` path. An empty original name
|
||||
uses endpoint/global configuration; it does not select `origin` implicitly.
|
||||
3. Preserve `QueryTag`'s endpoint-resolution verification and exact full/peeled
|
||||
ref handling. Reading transport settings requires no current fetch/push URL
|
||||
equality check. Missing settings, including after a remote is renamed or
|
||||
removed, permit observation; actual configuration-read failures retain their
|
||||
causes. Reuse the supported transport keys already implemented. Mark
|
||||
configuration reads and scoped-URL configuration resolution at the Git
|
||||
boundary so failures map to `transport_configuration` independently of the
|
||||
optional remote name. Positively detected rewrites use `endpoint_changed`;
|
||||
other operational failures use `remote_observation`, with cancellation taking
|
||||
its existing precedence and causes retained throughout.
|
||||
4. Extract classification so the immediate path can supply its already captured
|
||||
endpoint and transport settings. Keep `reconcilePush` as the publication
|
||||
adapter: retain its existing detached, bounded recovery context, phase
|
||||
transitions, and original push-error chain. A successful exact match still
|
||||
resolves that push error. Successful pushes continue to return directly.
|
||||
5. The later path uses the caller's context for configuration reads and the
|
||||
observation. Use the evidence's `RepositoryDir` as Git working-directory
|
||||
context; an empty value uses the caller's current directory. This preserves
|
||||
the meaning of relative local endpoints in automatically captured records.
|
||||
An unavailable recorded directory produces an unresolved observation with
|
||||
actionable context. Recovery does not discover identity from current HEAD,
|
||||
local tags, module files, profiles, or current remote URLs.
|
||||
|
||||
Use this classification order for later recovery:
|
||||
|
||||
| Evidence or observation | State and error behavior |
|
||||
| --- | --- |
|
||||
| Missing endpoint or full tag | `unknown`, with `missing_evidence`; list the missing fields and perform no remote query. |
|
||||
| Invalid supplied full object ID or invalid Git tag ref | `unknown`, with `invalid_evidence`; perform no remote query. |
|
||||
| Cancellation or deadline prevents completion | `unknown`, with `canceled` and the context cause. |
|
||||
| Endpoint rewriting changes the captured URL | `unknown`, with `endpoint_changed`; do not query the substituted destination. |
|
||||
| Transport-configuration read fails | `unknown`, with `transport_configuration`. |
|
||||
| Query fails or returns unreadable evidence | `unknown`, with `remote_observation`. |
|
||||
| Exact-ref records are inconsistent | `unknown`, with `inconsistent_observation`. |
|
||||
| Full tag is authoritatively absent | `not_published`; the observation completed successfully, even if tag-object or candidate-commit evidence is missing. |
|
||||
| Full tag is present and intended tag-object ID is missing | `unknown`, with `missing_evidence`, including when a commit matches. |
|
||||
| Observed tag-object ID differs from the intended ID | `conflict`; include both IDs. |
|
||||
| Tag-object IDs match and no peeled ref is available | `published`, preserving existing behavior. |
|
||||
| Tag-object IDs match, a peeled ref is available, and candidate commit is missing | `unknown`, with `missing_evidence`. |
|
||||
| Tag-object IDs match and the peeled commit matches the candidate | `published`. |
|
||||
| Tag-object IDs match but the peeled commit differs | `conflict`; include expected and observed commit IDs as well as tag-object IDs. |
|
||||
|
||||
Reuse existing full SHA-1/SHA-256 ID and Git-ref validation through internal
|
||||
helpers. Compare hexadecimal IDs by value, accepting their existing supported
|
||||
letter cases; retain supplied evidence for reporting. Module/version labels and
|
||||
recording time are context, rather than additional observation prerequisites.
|
||||
Distinguish a positively invalid ref from tooling, directory, configuration,
|
||||
or cancellation failures while checking it; operational failures retain their
|
||||
appropriate unresolved cause. Expose the existing diagnostic formatter through
|
||||
an internal Git helper for shared error/presentation use, preserving wrapped
|
||||
causes and the current credential-redaction behavior.
|
||||
|
||||
For the later API, a completed observation of `published`, `not_published`, or
|
||||
`conflict` returns a nil error. An unresolved observation returns an error and
|
||||
its `unknown` result. The immediate publication adapter continues to preserve
|
||||
the original push error for known absence/conflict and unresolved recovery.
|
||||
|
||||
**Verification:** Extend existing classification tests for the partial-evidence
|
||||
matrix and expected/observed IDs. Reuse existing Git parser tests for unchanged
|
||||
duplicate-ref and peeled-only behavior. Add focused Git-boundary coverage for
|
||||
current transport settings, repaired settings, removed/renamed remotes, fixed
|
||||
URLs, and configuration-read failure. Preserve the existing lost-acknowledgment,
|
||||
rejected-push, endpoint-change, and cancellation behavior.
|
||||
|
||||
**R16 follow-up completed:** Configuration, rewrite, and operational failures
|
||||
map by their known cause, with facade/CLI coverage for named and unnamed
|
||||
configuration, positive rewrites, and tooling failures. Setup failure and rewrite
|
||||
mismatch tests confirm that no remote tag query occurs.
|
||||
|
||||
## Stage 2: Implement private attempt records
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** A supported internal codec can save and load a single attempt
|
||||
without relying on diagnostic output.
|
||||
|
||||
1. Add record encoding and storage helpers under `internal/publish`. Use a JSON
|
||||
envelope with integer `format_version` and an `evidence` object. Ordinary
|
||||
UTF-8 evidence uses version 1; non-UTF-8 fields use version 2 and a separate
|
||||
`evidence_bytes` object of base64 strings, preserving exact supported bytes.
|
||||
See the [canonical format](../integrations/publication-records.md#private-attempt-record).
|
||||
The evidence fields
|
||||
are `repository_dir`, `module_dir`, `module_path`, `version`, `tag`,
|
||||
`candidate_commit`, `tag_object`, `endpoint`, `remote`, and `recorded_at`.
|
||||
Store the exact endpoint. Fields other than the envelope version/object can
|
||||
be missing in a partial record. Retain the evidence available at capture;
|
||||
loading does not fill omissions from current repository state.
|
||||
2. Decode one complete JSON document. Missing/unsupported envelope versions,
|
||||
malformed JSON, and structural type errors are `record_format` failures.
|
||||
Accept additional fields within a supported version. Semantic evidence
|
||||
completeness is evaluated by reconciliation, so a readable partial record
|
||||
remains usable for discovery and any classification it can support. Decode
|
||||
known version-2 byte fields as strings, then decode standard base64. Invalid
|
||||
structural types and malformed base64 return `record_format` with their
|
||||
available causes. Additional unknown fields retain their existing handling.
|
||||
3. Resolve the default directory with
|
||||
`git rev-parse --path-format=absolute --git-common-dir`, then append
|
||||
`releasekit/publication-attempts`. Use Git metadata discovery rather than
|
||||
assuming `.git` is a directory. This shares saved attempts across linked
|
||||
worktrees of the repository. Repository discovery for storage needs no
|
||||
candidate branch, cleanliness, or HEAD check.
|
||||
4. Treat the override as a directory. An absolute override is used directly;
|
||||
a relative override is resolved against the source repository root. Record
|
||||
files have distinct `attempt-<random>.json` names. Use standard-library random
|
||||
naming and exclusive file creation to keep concurrent calls and retries from
|
||||
overwriting each other. The filename supplies uniqueness; time is discovery
|
||||
metadata, recorded once per attempt as UTC RFC3339Nano text.
|
||||
5. Create ReleaseKit-owned directories privately and files with owner-only
|
||||
permissions, using platform-supported permissions. Apply this file handling
|
||||
equally to default and overridden storage. Keep unrelated existing directory
|
||||
permissions intact. Use ordinary encoding, writes, closes, and failed-write
|
||||
cleanup; this is a small evidence file with the durability limits in the
|
||||
feature roadmap. Report write/close failures rather than presenting a failed
|
||||
write as a saved record. Report an absolute record path on successful save.
|
||||
6. Preserve publication's existing clean-worktree requirement when resolving an
|
||||
override. Git metadata, external locations, and Git-ignored locations can
|
||||
retain records. If writing the proposed record would introduce unignored
|
||||
worktree contents, report `record_write` and leave that record unwritten.
|
||||
Use the actual proposed file path and Git's ignore rules before creating
|
||||
record contents; account for ordinary filesystem path resolution. This is a
|
||||
recording failure, not a new publication prerequisite or a reason to change
|
||||
ignore configuration automatically. Do not modify tracked files or exclusion
|
||||
rules to accommodate storage.
|
||||
7. File-open/read failures are `record_read`; storage-resolution and save
|
||||
failures are `record_write`. Preserve underlying causes through wrapping.
|
||||
Error displays identify paths without echoing record contents. Retention and
|
||||
manual removal remain operator-owned.
|
||||
|
||||
**Verification:** Use real temporary storage for complete/partial round trips,
|
||||
unsupported versions, malformed data, read/write failures, unique attempts,
|
||||
default and override paths, and permissions where supported. Verify linked
|
||||
worktree metadata resolution and clean-tree preservation for an external,
|
||||
ignored, and unignored override. Use injected failures at the small file/Git
|
||||
boundary when platform permissions cannot reliably induce a failure. Verify
|
||||
that private records retain synthetic credentials while diagnostics redact them.
|
||||
|
||||
**R17 follow-up completed:** Known byte fields validate their string representation
|
||||
before base64 decoding. Tests cover valid/empty strings, numeric arrays, other
|
||||
structural errors, and retained JSON/base64 causes. Existing all-field byte
|
||||
round trips and original-versus-decoy Git endpoint observations remain covered.
|
||||
|
||||
## Stage 3: Add the facade-owned recovery interfaces
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** Go callers can pass publication evidence directly to recovery or
|
||||
load it through ReleaseKit.
|
||||
|
||||
Define these root-facade interfaces, with ordinary conversions to internal types:
|
||||
|
||||
```go
|
||||
type PublicationEvidence struct {
|
||||
RepositoryDir string
|
||||
ModuleDir string
|
||||
ModulePath string
|
||||
Version string
|
||||
Tag string
|
||||
CandidateCommit string
|
||||
TagObject string
|
||||
Endpoint string
|
||||
Remote string
|
||||
RecordedAt string
|
||||
}
|
||||
|
||||
type PublicationReconciliationResult struct {
|
||||
Evidence PublicationEvidence
|
||||
State PublicationState
|
||||
ObservedTagObject string
|
||||
ObservedCommit string
|
||||
}
|
||||
|
||||
type PublicationRecoveryError struct {
|
||||
Kind string
|
||||
RecordPath string
|
||||
MissingFields []string
|
||||
// Private cause, with Error and Unwrap methods.
|
||||
}
|
||||
|
||||
func ReconcilePublication(context.Context, PublicationEvidence) (PublicationReconciliationResult, error)
|
||||
func LoadPublicationEvidence(string) (PublicationEvidence, error)
|
||||
```
|
||||
|
||||
1. Implement `ReconcilePublication` using Stage 1 and the existing public
|
||||
publication states. It performs observation only; Stage 1 defines its result
|
||||
and error semantics. `LoadPublicationEvidence` uses Stage 2 and reads the
|
||||
explicitly supplied path, relative to the caller's working directory when
|
||||
relative. It performs no Git discovery or remote access.
|
||||
2. Implement the facade-owned error with pointer receivers for `Error` and
|
||||
`Unwrap`. Convert internal recovery errors while retaining original causes
|
||||
for `errors.Is`/`errors.As`. Document `Kind` values from Stages 1 and 2 plus
|
||||
`storage_enumeration` from Stage 5. `MissingFields` uses the record field
|
||||
names. Provide credential-safe error text through the existing Git diagnostic
|
||||
redaction machinery; raw evidence and unwrapped causes remain sensitive.
|
||||
3. Add `PublicationRecordDir string` to `ReleaseRequest`, documenting it as a
|
||||
publication-only directory override that `Check` ignores. Add
|
||||
`Evidence PublicationEvidence`, `RecordPath string`, and `RecordError error`
|
||||
to `PublicationResult`. Preserve existing flat fields and their behavior;
|
||||
populate them and the new evidence from the same captured values. Stage 4
|
||||
wires recording into publication. `Publish` is the supported record producer.
|
||||
4. Keep serialization in the internal codec. The facade's evidence is the
|
||||
reusable typed input, not the CLI's diagnostic JSON representation. Document
|
||||
its raw endpoint and the sensitive nature of retained records.
|
||||
|
||||
**Verification:** Add external-package API tests for direct evidence recovery,
|
||||
record loading, partial evidence, preserved causes, and state/error semantics.
|
||||
Include a caller using `result.Evidence` and a caller using the loader's result
|
||||
without copying fields. Extend the existing facade checks only as needed to
|
||||
protect ownership of these types and existing dependency directions; keep the
|
||||
current external-type allowances.
|
||||
|
||||
## Stage 4: Record attempts inside shared publication
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** API and CLI publication retain the same evidence before pushing,
|
||||
with recording failures separate from the publication outcome.
|
||||
|
||||
1. Wire the new request/result fields through the facade and
|
||||
`internal/publish.Request`/`Result`. Keep recording in `internal/publish`.
|
||||
Use named result returns in the internal publication operation so finalization
|
||||
can add record information to every returned result without replacing the
|
||||
publication error.
|
||||
2. Initialize attempt metadata when candidate capture has established the source
|
||||
repository, including when capture returns partial evidence with an error.
|
||||
Capture the actual effective remote name, using publication's existing
|
||||
`origin` default. Discover storage locally and retain any storage failure
|
||||
separately. An invocation that fails before its repository is known returns
|
||||
its available typed evidence with no initialized record or recording error.
|
||||
3. Populate evidence from the captured candidate, resolved endpoint, and created
|
||||
tag-object ID as those become available. Preserve tag-object evidence returned
|
||||
alongside a tag-inspection failure. Use the stable attempt timestamp from
|
||||
initialization and the same evidence value in the result and saved record.
|
||||
4. Save one evidence snapshot after the final existing pre-push guards and
|
||||
before calling `PushTag`. On an earlier ordinary return, save the available
|
||||
partial snapshot during finalization instead. Finalization runs once for
|
||||
recording purposes; it does not retry or create another attempt after a
|
||||
failed save. An interrupted push can therefore leave a complete saved identity
|
||||
even when the process never returns a publication result.
|
||||
5. Preserve the original publication return value and error when storage fails.
|
||||
Supply the wrapped recording failure in `RecordError`, with `RecordPath`
|
||||
populated only for a saved record. A successful push with a failed save still
|
||||
returns publication success. A failed/unknown push with a saved record keeps
|
||||
its existing state and original error chain.
|
||||
6. Records contain original identity and discovery metadata. Later observations
|
||||
and publication's final outcome remain separate results. Reconciliation and
|
||||
listing read saved records without updating them. Each later publication
|
||||
invocation gets its own attempt; existing records are retained.
|
||||
|
||||
**Verification:** Extend public publication fixtures to assert that the record
|
||||
exists and contains the intended endpoint, full tag, commit, and tag object
|
||||
before the push subprocess proceeds. Exercise success, rejected push, unresolved
|
||||
push, pre-push failure, tag-inspection failure, and storage failure. Verify
|
||||
unchanged publication outcomes/cause chains, one useful partial record on early
|
||||
failure, distinct records for retries/modules, and no standard-suite duplication.
|
||||
Verify that default storage and a failed unignored override keep the candidate
|
||||
worktree clean. Cover a non-nil `RecordError` on successful publication through
|
||||
both API results and Stage 7's CLI integration, preserving publication success.
|
||||
|
||||
## Stage 5: Provide local attempt discovery
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** Callers can find saved attempts even after losing publication
|
||||
output, with useful partial discovery when a record is damaged.
|
||||
|
||||
Add these facade-owned types and helper, backed by `internal/publish`:
|
||||
|
||||
```go
|
||||
type PublicationAttemptsRequest struct {
|
||||
RepositoryDir string
|
||||
RecordDir string
|
||||
}
|
||||
|
||||
type PublicationAttempt struct {
|
||||
RecordPath string
|
||||
Evidence PublicationEvidence
|
||||
}
|
||||
|
||||
type PublicationAttemptsResult struct {
|
||||
RecordDir string
|
||||
Attempts []PublicationAttempt
|
||||
Diagnostics []*PublicationRecoveryError
|
||||
}
|
||||
|
||||
func ListPublicationAttempts(context.Context, PublicationAttemptsRequest) (PublicationAttemptsResult, error)
|
||||
```
|
||||
|
||||
1. An empty repository selection means the current directory. Resolve the
|
||||
default or relative override using Stage 2's repository/storage rules. An
|
||||
absolute `RecordDir` can be enumerated directly, including outside a checkout.
|
||||
Discovery neither creates the directory nor requires a release profile.
|
||||
2. Enumerate the selected directory's JSON record files at that level, using
|
||||
the shared loader. Return readable attempts with absolute paths, including
|
||||
partial records. Return a per-record recovery diagnostic for each unreadable,
|
||||
malformed, or unsupported record, and continue to other entries. Use a
|
||||
deterministic path order for presentation without introducing automatic
|
||||
chronological selection.
|
||||
3. Return a top-level `storage_enumeration` error for directory-resolution or
|
||||
enumeration failure, including a missing selected storage directory. Preserve
|
||||
already obtained entries/diagnostics when available. An existing readable
|
||||
empty directory returns an empty successful result. Cancellation uses the
|
||||
`canceled` kind and preserves partial results and its cause. Per-record failures
|
||||
alone populate `Diagnostics` with a nil top-level error.
|
||||
4. Listing reads saved identity and recording time. It performs no remote
|
||||
query, current-candidate capture, local-tag inspection, recovery selection,
|
||||
or cleanup. Error text follows Stage 3's credential-safe behavior.
|
||||
|
||||
**Verification:** Cover multiple modules/retries, empty storage, mixed readable
|
||||
and damaged records, unsupported versions, inaccessible/missing directories,
|
||||
explicit external storage, and cancellation. Verify no remote access and no
|
||||
record mutation. Test public result/error distinctions and preserved causes;
|
||||
reuse codec cases rather than repeating all parser permutations here.
|
||||
|
||||
## Stage 6: Add CLI recovery, discovery, and structured output
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** Operators and automation can discover and reconcile explicitly
|
||||
selected attempts without profiles or diagnostic parsing.
|
||||
|
||||
Implement these command forms in the existing parser and CLI dispatch:
|
||||
|
||||
```text
|
||||
releasekit reconcile --record FILE [--json]
|
||||
releasekit publication-attempts [--repo DIR] [--record-dir DIR] [--json]
|
||||
```
|
||||
|
||||
1. Add command/argument fields to `cli.Invocation` and extend usage/parser tests.
|
||||
Neither command takes a version positional argument or loads a profile.
|
||||
Reconciliation requires its explicit record path. The loader resolves a
|
||||
relative record path from the invoking directory; the evidence supplies the
|
||||
original repository context for observation. Listing uses Stage 5's directory
|
||||
rules. Route these commands before `main.run`'s current unconditional
|
||||
repository-root lookup for other non-version commands.
|
||||
2. Place recovery presentation in a focused CLI source file rather than growing
|
||||
candidate/build/delivery handlers. Invoke the root facade for load, observe,
|
||||
and list mechanics. A record-read/format error preserves the requested path
|
||||
and produces an invocation error. A readable partial record reaches the
|
||||
observer and can produce `unknown` or a supported known observation.
|
||||
3. Human output shows the selected path, safe identity, state, available observed
|
||||
IDs, and actionable cause context. Unknown diagnostics identify missing
|
||||
fields, invalid evidence, changed endpoint rewriting, transport-configuration
|
||||
failure, cancellation, or failed/inconsistent observation using the typed
|
||||
recovery error. Show expected and observed IDs for conflicts, including
|
||||
commit evidence when that is what conflicts. Explain possible next actions
|
||||
from known causes, without guessing that a generic Git failure is an
|
||||
authentication or connectivity failure.
|
||||
Configuration failures include endpoint/global settings when the remote name
|
||||
is absent. Use the same cause-based recovery kinds from Stage 1 for JSON
|
||||
diagnostics and human guidance.
|
||||
4. Human listing shows path, module identity, full tag, candidate commit, and
|
||||
recording time when available. Show per-record diagnostics alongside usable
|
||||
entries and make incomplete discovery clear. Distinguish an enumeration
|
||||
failure from a successfully enumerated empty directory.
|
||||
5. Implement explicit CLI JSON DTOs. After successful argument parsing,
|
||||
`--json` writes one JSON document to stdout for success and failure paths;
|
||||
normal usage/argument-parse errors retain the existing stderr behavior.
|
||||
Use these fields, with `schema_version: 1` and the command name:
|
||||
|
||||
| Command | JSON fields beyond `schema_version` and `command` |
|
||||
| --- | --- |
|
||||
| `reconcile` | `record_path`, safe `evidence`, `state`, `observed_tag_object`, `observed_commit`, and `diagnostic` when unresolved or loading fails. Omit observation fields when loading failed. |
|
||||
| `publication-attempts` | `record_dir`, `attempts` (each with `record_path` and safe `evidence`), `diagnostics`, and a top-level `diagnostic` for enumeration/cancellation failure. Arrays are present, including when empty. |
|
||||
|
||||
Safe evidence uses the Stage 2 snake-case field names with credential-safe
|
||||
string displays. A diagnostic contains `kind`, `message`, `record_path` when
|
||||
applicable, and `missing_fields` when applicable. Encode error text explicitly;
|
||||
keep raw Go errors and private record JSON out of these DTOs. Apply existing
|
||||
URL/diagnostic redaction before JSON encoding, including nested messages.
|
||||
Raw endpoint preservation belongs only to private records and typed evidence.
|
||||
Human and JSON output use the same state/diagnostic interpretation.
|
||||
6. Use these process statuses and document their distinct meanings:
|
||||
|
||||
| Command/result | Status |
|
||||
| --- | --- |
|
||||
| Reconciliation confirms publication | 0 |
|
||||
| Reconciliation observes absence or conflict | 1 |
|
||||
| Reconciliation invocation, selected-record read, or format failure | 2 |
|
||||
| Reconciliation remains unknown, including missing evidence or canceled observation | 3 |
|
||||
| Discovery completes with readable entries and no diagnostics, including an empty existing store | 0 |
|
||||
| Discovery is incomplete or fails, including cancellation | 1 |
|
||||
| Discovery argument/usage error | 2 |
|
||||
|
||||
A nil Go observation error for absence/conflict still maps to CLI status 1.
|
||||
Preserve the original context or filesystem cause in the API even when its
|
||||
CLI status is an invocation classification.
|
||||
|
||||
**Verification:** Add parser/dispatch tests and command tests for human/JSON
|
||||
state parity, exit classifications, mandatory selection, unusable records,
|
||||
partial discovery, and credential redaction. Assert semantic fields rather than
|
||||
complete diagnostic wording. Test paths with spaces/apostrophes and running
|
||||
record-based recovery from another working directory. Verify these commands
|
||||
work with a dirty worktree, changed branch/HEAD, removed or moved local tag,
|
||||
and unavailable profile, with no candidate commands or hosted requests.
|
||||
|
||||
## Stage 7: Integrate publication's record override and recovery handoff
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** Existing publication commands expose saved evidence and recording
|
||||
failures without additional consumer release infrastructure.
|
||||
|
||||
1. Add `--record-dir DIR` to `publish`, mapping it to
|
||||
`ReleaseRequest.PublicationRecordDir`. Keep the automatic default sufficient
|
||||
for ordinary callers. The override is a CLI/API option; profile JSON remains
|
||||
unchanged for this feature. The flags for other existing commands retain
|
||||
their meanings.
|
||||
2. After publication, report a saved absolute record path or a credential-safe
|
||||
recording warning on stderr, separately from the existing publication
|
||||
summary/error. These messages leave publication's exit classification and
|
||||
original failure diagnostics intact, including success with recording failure.
|
||||
3. When publication returns an error at tag creation, push, or immediate
|
||||
reconciliation and a record was saved, print a copyable `reconcile --record`
|
||||
command selecting that exact path. Partial records can explain missing
|
||||
evidence. Earlier failures still report their saved path when available.
|
||||
Successful publication reports the path without prescribing a recovery step.
|
||||
4. Prefer the consumer module's registered Go tool for the printed command.
|
||||
API-only consumers use durable private module/checksum files under Git
|
||||
metadata, with a separate bootstrap module root so consumer vendoring does
|
||||
not govern tool resolution. Retain the selected requirements, checksums,
|
||||
versioned replacements, and original locations of local replacements.
|
||||
Consumers without a selected ReleaseKit dependency use the native executable
|
||||
path. Select the absolute record path explicitly and quote every argument
|
||||
for the POSIX-shell runbooks, including apostrophes. The command survives a
|
||||
cold Go-tool executable's removal and launcher-owned metadata cleanup.
|
||||
Explain how to use another selected supported CLI if an entry point becomes
|
||||
unavailable.
|
||||
5. Prepare the command after publication using a separate bounded context so
|
||||
publication cancellation leaves handoff construction available. Report
|
||||
module-preparation failures separately and retain a persistent native
|
||||
executable fallback. Recognized temporary Go-build executables provide no
|
||||
durable native fallback. Keep publication's original status/error and
|
||||
immediate reconciliation context unchanged.
|
||||
|
||||
**R14/R15 follow-ups completed:** API-only recovery uses an independent private
|
||||
module root; post-failure preparation handles cancellation and native fallback.
|
||||
|
||||
**Verification:** Extend existing publication CLI tests for the default, override,
|
||||
recording warning with successful status, and original failed/unknown status.
|
||||
Use a built CLI in an assembled test to execute the printed recovery command
|
||||
and verify it selects the original record among retries/modules. Test shell
|
||||
argument quoting with synthetic paths. Keep the existing module-selected Go-tool
|
||||
launchers and source bootstrap working without adding consumer-owned recording.
|
||||
Include an API-only consumer that vendors the library without declaring the CLI
|
||||
tool; execute its handoff and verify source/vendor contents remain unchanged.
|
||||
Exercise relative local replacements, versioned replacements, and ReleaseKit's
|
||||
own source-module recovery entry point.
|
||||
Coordinate cancellation at the recorded push boundary for native, cold
|
||||
registered-tool, and temporary-launcher invocations. Execute the resulting
|
||||
handoff after driver/launcher cleanup and verify retained publication diagnostics
|
||||
and recovery statuses. Exercise native fallback after module-preparation failure.
|
||||
|
||||
## Stage 8: Finish assembled coverage and canonical documentation
|
||||
|
||||
**Completed:** 2026-11-02
|
||||
|
||||
**Outcome:** The full feature works across process boundaries, with supported
|
||||
contracts and a short consumer recovery workflow.
|
||||
|
||||
1. Add focused assembled tests for publication with a failed/interrupted push,
|
||||
lost output followed by discovery, and a separate later recovery invocation.
|
||||
Use a controlled local Git boundary to know when pre-push recording finished;
|
||||
coordinate cancellation/interruption without arbitrary sleeps. Remove or move
|
||||
the local tag and change branch, HEAD, remote URLs, or transport settings
|
||||
before later recovery. Cover root/nested tags and original-record selection.
|
||||
Retain the assembled vendored API-only handoff coverage added for R14,
|
||||
alongside cold registered-tool and temporary-launcher recovery.
|
||||
R15 adds canceled publication and preparation-failure handoff cases using
|
||||
explicit push readiness and fresh later invocations.
|
||||
R16 covers setup failure classifications and human/JSON guidance without
|
||||
querying an invalid or substituted endpoint.
|
||||
R17 adds byte-field structural and encoding failures at the codec boundary,
|
||||
reusing the existing partial-record and lossless-observation fixtures.
|
||||
2. Establish read-only behavior through resulting refs, worktree/branch state,
|
||||
saved records, and controlled observations of candidate/hosted boundaries.
|
||||
Reuse lower-level classification and codec tests. Tests use local remotes,
|
||||
temporary storage, synthetic credentials, and isolated environments; live
|
||||
services and real credentials are unnecessary.
|
||||
3. Update GoDoc for evidence, results, errors, request fields, and all new helpers.
|
||||
Clarify existing publication-state comments where they currently describe
|
||||
only successful pushes or a single invocation, while preserving those
|
||||
states' established immediate-publication behavior.
|
||||
4. Update documentation at its canonical owners:
|
||||
|
||||
| Owner | Change |
|
||||
| --- | --- |
|
||||
| `docs/cli.md` | New commands, flags, explicit selection, relative-path semantics, human/structured modes, and statuses; update the command inventory. |
|
||||
| `docs/consumers/publish.md` | Automatic recording, separate recording failures, reusable typed evidence, loader/discovery use, optional later observation, storage placement/lifecycle, overrides, permissions, retention, and sensitive-data handling. |
|
||||
| New `docs/integrations/publication-records.md` | Versioned private-record and CLI JSON formats and unsupported-format handling. Link to CLI/GoDoc for operation contracts and the publication guide for physical placement/lifecycle. |
|
||||
| `docs/release.md` | Replace manual endpoint-copy/comparison instructions with explicit-record recovery and local discovery; describe lost/older evidence and unavailable original executable. |
|
||||
| `docs/internal/overview.md` | Implemented observer, recording, discovery, facade, and CLI responsibilities; link to canonical formats and contracts. |
|
||||
|
||||
The runbook must explain that current absence does not prove a version was
|
||||
never published or permit reuse of a known published version. Keep cleanup
|
||||
and retry choices explicit and preserve immutable published tags/contents.
|
||||
Storage failure loses the saved handoff, rather than changing the actual
|
||||
publication outcome. Explain that direct typed evidence can still support
|
||||
recovery when recording fails. Document the limits of abrupt process
|
||||
termination before recording, removed storage, and older attempts lacking
|
||||
sufficient retained identity. Later recovery adds no successful-publication
|
||||
read-back, polling, CI, installation, or hosted-delivery gate.
|
||||
5. Review examples, local links, fragment destinations, privacy language, and
|
||||
consistency with the feature roadmap. Keep release-version selection and
|
||||
tagging in later release preparation. Update any existing draft release note
|
||||
only when its version/scope has been deliberately selected for this feature.
|
||||
6. Once the assembled implementation is ready, run `go test ./...` and
|
||||
`go run ./internal/apicheck` with the prepared compatible local Go toolchain
|
||||
and offline dependencies. Run focused tests while implementing their owners;
|
||||
reuse successful checks for unchanged content. Add targeted race detection
|
||||
if new shared mutable state or concurrent record tests expose a distinct risk.
|
||||
Finish with `git diff --check`, review all implementation/test/doc changes,
|
||||
and complete the repository's normal commit discipline.
|
||||
|
||||
The sprint is complete when automatic API/CLI recording, direct and loaded
|
||||
evidence reconciliation, partial local discovery, current transport settings
|
||||
with fixed original endpoints, safe human/structured output, and the assembled
|
||||
recovery workflow satisfy this plan and the feature roadmap. The final report
|
||||
should identify implemented interfaces, validation results, and any remaining
|
||||
limitations. Subsequent architecture review and release/testing decisions take
|
||||
place after this complete initial implementation.
|
||||
|
||||
**Follow-up verification completed:** R14–R17 corrections and their assigned
|
||||
regression coverage are complete. The vendored API-only workflow runs through
|
||||
a private Go-tool launcher, removes the original launcher metadata, and then
|
||||
executes its printed recovery command. Native and cold Go-tool cancellation,
|
||||
fallback, setup-error classifications, and byte-field format validation are
|
||||
covered. The full offline suite and compiled facade/dependency checks passed
|
||||
with Go 1.26.8; prose whitespace, local links, and fragment destinations were
|
||||
checked. See [remediation completion records](remediation.md).
|
||||
@@ -1,80 +0,0 @@
|
||||
# Promptkit Feature Request: Read-Only Publication Reconciliation
|
||||
|
||||
## Consumer Context
|
||||
|
||||
Promptkit uses ReleaseKit v0.7.0 as a module-selected Go tool for candidate
|
||||
validation, source-tag publication, and page-only Gitea delivery. Source
|
||||
publication and hosted delivery have separate outcomes. A successful intended
|
||||
tag push completes the source release without waiting for CI or module
|
||||
availability.
|
||||
|
||||
ReleaseKit already reconciles an unsuccessful push with a bounded authoritative
|
||||
query. This request concerns later recovery when that observation remains
|
||||
unknown and the maintainer can investigate again after connectivity or
|
||||
authentication is restored.
|
||||
|
||||
## Current Pain Point
|
||||
|
||||
Promptkit's runbook reproduces a manual recovery procedure: retain the captured
|
||||
publication endpoint, version, candidate commit, and tag object; check that Git
|
||||
URL rewriting still resolves the endpoint to itself; query the exact tag; then
|
||||
interpret whether the original tag was published, absent, conflicting, or still
|
||||
unobservable.
|
||||
|
||||
That procedure is generic release logic, rather than Promptkit-specific policy.
|
||||
Keeping it in each consumer's instructions adds maintenance and opportunities
|
||||
for mistakes. Querying the current remote instead of the original captured
|
||||
endpoint can establish the wrong outcome after configuration changes.
|
||||
|
||||
Credential-safe diagnostics introduce an additional handoff issue: a displayed
|
||||
HTTP(S) endpoint can have its userinfo redacted. That display is not necessarily
|
||||
a usable transport URL, and asking an operator to reconstruct the original
|
||||
endpoint from it is inconvenient. Consumers should not need to parse diagnostic
|
||||
text or independently implement ReleaseKit's Git observation rules.
|
||||
|
||||
## Requested Capability
|
||||
|
||||
Please consider a first-class, read-only CLI operation for reconciling a previous
|
||||
source-publication attempt. A name such as `reconcile` or `publication-status`
|
||||
would be suitable; the exact command and interface are for ReleaseKit to choose.
|
||||
A corresponding public Go API could serve library consumers with the same need.
|
||||
|
||||
The capability would use the original attempt's evidence, including the exact
|
||||
captured endpoint, complete Git tag, intended tag-object identity, and candidate
|
||||
commit as applicable. It should:
|
||||
|
||||
- Classify confirmed publication, authoritative absence, a conflicting tag, and
|
||||
an unresolved observation using ReleaseKit's existing publication semantics.
|
||||
- Preserve the distinction between the annotated tag object and its peeled
|
||||
source commit; a commit match alone does not establish the intended tag object.
|
||||
- Account for endpoint rewriting and configuration changes rather than silently
|
||||
substituting the current remote destination. An observation failure or
|
||||
insufficient original evidence should remain unresolved.
|
||||
- Offer a practical handoff of attempt evidence without requiring consumers to
|
||||
parse human-readable output or retype a credential-bearing URL from a
|
||||
redacted diagnostic. Authentication can remain locally supplied, and displayed
|
||||
endpoints should retain the existing credential-safe treatment.
|
||||
- Return documented, actionable outcomes for operators and automation.
|
||||
|
||||
Promptkit has no preference for a particular evidence-storage format or internal
|
||||
implementation. Explicit inputs or a reusable attempt record could meet the
|
||||
need; the important property is that recovery refers to the original attempt.
|
||||
|
||||
## Scope And Workflow
|
||||
|
||||
This is a diagnostic operation after a failed or uncertain publication. It
|
||||
would observe remote state without creating, pushing, moving, or deleting tags;
|
||||
changing branches; publishing hosted pages; or rerunning candidate validation.
|
||||
Any subsequent corrective action would remain explicit.
|
||||
|
||||
It would not become a read-back gate after a successful push or a prerequisite
|
||||
for ordinary publication. CI status, consumer retrieval, and hosted-page delivery
|
||||
would remain separate concerns. There is no request for a combined publish-and-
|
||||
deliver command, automatic tag cleanup, or a new release approval step.
|
||||
|
||||
## Expected Benefit
|
||||
|
||||
Promptkit could replace its copied Git query and comparison instructions with
|
||||
one upstream-owned command and a short recovery link. Other ReleaseKit consumers
|
||||
could use the same classifications and credential-safe evidence handoff, reducing
|
||||
both duplicated instructions and divergent recovery behavior.
|
||||
@@ -1,350 +0,0 @@
|
||||
# Roadmap: read-only publication reconciliation
|
||||
|
||||
Status: planned capability; storage, selection, credential handling, and output
|
||||
decisions resolved.
|
||||
|
||||
## Purpose
|
||||
|
||||
Let maintainers revisit a failed or uncertain source-publication attempt through
|
||||
one ReleaseKit operation. PromptKit currently copies the endpoint checks, exact
|
||||
tag query, and result interpretation into its runbook. ReleaseKit should own
|
||||
these mechanics and the handoff of original attempt evidence.
|
||||
|
||||
The target includes a public Go API and a CLI command with equivalent behavior.
|
||||
This roadmap describes the end state and required work, without selecting a
|
||||
release version or prescribing a staged implementation plan.
|
||||
|
||||
## Current baseline
|
||||
|
||||
The current checkout contains the v0.7.0 release notes and publication workflow.
|
||||
The [publication guide](../consumers/publish.md) describes existing outcomes;
|
||||
the [maintainer runbook](../release.md#resolve-publication-failures) describes manual recovery.
|
||||
|
||||
- `PublicationResult` supplies the repository and module identity, semantic
|
||||
version, full tag, candidate commit, captured endpoint, tag object when known,
|
||||
phase, and publication state. Its endpoint can contain credentials.
|
||||
- Internal `reconcilePush` makes a bounded authoritative observation after a
|
||||
failed push. It compares the annotated tag object and available peeled commit
|
||||
evidence, returning published, absent, conflicting, or unknown outcomes.
|
||||
- `internal/git.Endpoint.QueryTag` verifies the captured endpoint's scoped URL
|
||||
resolution before querying the full tag and peeled ref. Inconsistent exact-ref
|
||||
records remain unresolved.
|
||||
- The temporary remote carries selected remote-specific transport settings,
|
||||
including proxy, upload-pack, and server options, alongside the captured URL.
|
||||
- The CLI masks HTTP(S) endpoint userinfo in diagnostics. The runbook still asks
|
||||
maintainers to copy the displayed endpoint and compare remote object IDs.
|
||||
- There is no public operation or CLI command for later reconciliation, and no
|
||||
reusable CLI attempt record for preserving the exact transport endpoint.
|
||||
|
||||
These existing observations and classifications provide the implementation
|
||||
foundation. The main additions are a reusable operation, original-evidence
|
||||
handoff, and documented operator and automation interfaces.
|
||||
|
||||
## Targeted end state
|
||||
|
||||
After connectivity or authentication is restored, a maintainer can give
|
||||
ReleaseKit the original publication evidence and receive a fresh observation
|
||||
of that attempt's destination and full tag. Library consumers use typed evidence;
|
||||
CLI consumers use a record produced by ReleaseKit without parsing diagnostics.
|
||||
Both Go API and CLI publication automatically retain attempt records through the
|
||||
shared publication path. Recovery selects a stored record explicitly; Go callers
|
||||
may also supply sufficient retained typed evidence directly.
|
||||
Maintainers can discover saved attempts through a local, read-only listing when
|
||||
the original publication output is unavailable. Discovery leaves record selection
|
||||
explicit.
|
||||
|
||||
| Observation | Reconciliation outcome |
|
||||
| --- | --- |
|
||||
| Intended annotated tag object, with consistent available commit evidence | Published |
|
||||
| Authoritative full tag absent | Not published |
|
||||
| Different tag object, including one pointing at the same commit | Conflict |
|
||||
| Observation failure, inconsistent records, or insufficient original evidence | Unknown |
|
||||
|
||||
The implementation preserves existing exact-object semantics, including current
|
||||
handling of an unavailable peeled ref. A matching commit alone is insufficient.
|
||||
Absence describes the current observation; it does not establish that the tag
|
||||
never existed. A record identifies the intended attempt and is not proof that a
|
||||
push happened or that the remote accepted it.
|
||||
|
||||
Reconciliation uses the original captured endpoint and full tag, including nested
|
||||
module prefixes. It checks that URL rewriting still permits observation of that
|
||||
endpoint. A changed remote configuration does not substitute a new destination.
|
||||
Authentication remains locally supplied through the supported Git mechanisms.
|
||||
The original remote name is optional transport context. Later reconciliation
|
||||
uses its current supported transport settings while keeping the recorded endpoint
|
||||
fixed, allowing operators to repair local configuration before observation.
|
||||
|
||||
The operation observes remote state without creating, pushing, moving, or
|
||||
deleting tags, changing branches, validating a candidate, or delivering hosted
|
||||
content. Current branch, worktree cleanliness, and current `HEAD` do not need to
|
||||
match publication prerequisites. Repository context may still be used for Git
|
||||
configuration and authentication. Corrective actions remain explicit.
|
||||
|
||||
Successful source publication retains its existing completion point. Later
|
||||
reconciliation is an optional recovery operation, with no routine read-back,
|
||||
polling, CI, consumer-installation, or delivery gate.
|
||||
|
||||
## Work needed
|
||||
|
||||
### 1. Extract a shared observation and classification path
|
||||
|
||||
**Rationale:** Immediate and later recovery should interpret the same evidence
|
||||
consistently.
|
||||
|
||||
**Target:** A reusable internal operation observes the captured endpoint and
|
||||
classifies its exact tag using the existing publication semantics.
|
||||
|
||||
**Work:** Separate remote observation and classification from the original push
|
||||
error and publication orchestration. Reuse the scoped endpoint and exact-ref
|
||||
query behavior. Keep immediate failed-push reconciliation bounded and preserve
|
||||
its existing error context. Later reconciliation should honor caller cancellation
|
||||
and return an unresolved outcome when observation cannot complete. It should not
|
||||
invoke the current-remote equality guards that belong to an active publication.
|
||||
Read the supported transport settings using the recorded remote name when
|
||||
available, and apply them to the temporary remote with the recorded endpoint.
|
||||
Keep the endpoint-resolution check. Missing remote settings permit observation
|
||||
with available endpoint/global configuration; configuration-read failures remain
|
||||
unresolved with their causes. Distinguish these cases from a changed destination.
|
||||
|
||||
### 2. Define public evidence and reconciliation results
|
||||
|
||||
**Rationale:** Consumers need supported types for recovery instead of rebuilding
|
||||
internal Git behavior or parsing diagnostic strings.
|
||||
|
||||
**Target:** A root-facade operation, provisionally `ReconcilePublication`, accepts
|
||||
original evidence and returns a useful observation with preserved error causes.
|
||||
|
||||
**Work:** Define the minimum evidence needed for each classification: captured
|
||||
endpoint, full tag, intended tag object, and candidate commit as applicable.
|
||||
Carry module/version information for operator context. Reuse existing publication
|
||||
state meanings while distinguishing the original attempt from the new observation.
|
||||
Define behavior for incomplete evidence and unsupported record formats without
|
||||
inferring missing identity from today's remote, tag, or profile. A deleted or
|
||||
moved local tag should not invalidate otherwise sufficient retained evidence.
|
||||
Expose the saved record location and any recording failure separately from the
|
||||
publication outcome. Accept retained typed evidence directly for reconciliation
|
||||
so observation does not depend on filesystem persistence.
|
||||
Return a single facade-owned evidence value from publication that reconciliation
|
||||
accepts directly. Provide a supported record-loading helper returning that same
|
||||
type so Go consumers can recover without copying fields or implementing JSON
|
||||
parsing.
|
||||
Include the original remote name as optional context in typed evidence and saved
|
||||
records. Recovery uses current connection settings rather than a stored snapshot
|
||||
of transport configuration; the recorded endpoint retains its identity role.
|
||||
|
||||
Keep facade ownership and internal Git boundaries. Exact type names, field names,
|
||||
and signatures belong in API design; no new public implementation package is
|
||||
needed for this feature.
|
||||
|
||||
### 3. Provide a practical attempt-evidence handoff
|
||||
|
||||
**Rationale:** Redacted diagnostics are useful displays but cannot reliably carry
|
||||
the original transport URL into a later process.
|
||||
|
||||
**Target:** Shared publication automatically retains a small versioned record
|
||||
for both Go API and CLI consumers. Library callers receive typed evidence,
|
||||
record location, and recording-failure information; the CLI presents that result.
|
||||
|
||||
**Work:** Capture evidence when the endpoint and tag object are established,
|
||||
before attempting the push. Preserve useful partial evidence if an earlier stage
|
||||
fails, clearly identifying what is missing. Make record production and loading
|
||||
available through supported interfaces. Keep original identity separate from
|
||||
later observations and avoid replacing one attempt's evidence with another's.
|
||||
Implement automatic private storage with an optional path override and the
|
||||
sensitive-data handling recorded below. Use ordinary file handling proportionate
|
||||
to recovery; this feature does not require a transactional journal or power-loss
|
||||
guarantees. Keep recording inside the shared publication path so library callers
|
||||
receive the same default and optional path override without consumer-owned hooks.
|
||||
|
||||
### 4. Expose recovery through the CLI and documentation
|
||||
|
||||
**Rationale:** A single command should replace copied query and comparison steps.
|
||||
|
||||
**Target:** A command provisionally named `reconcile` reads an explicitly selected
|
||||
attempt record and reports the outcome, selected full tag, intended identity,
|
||||
and actionable cause context. Automation has a documented structured result and
|
||||
exit behavior.
|
||||
|
||||
**Work:** Map the command to the facade, retaining credential-safe displays and
|
||||
errors. Document valid invocation, missing or unreadable records, unknown
|
||||
observations, and cancellation. Replace the runbook's manual endpoint-copy
|
||||
procedure with this workflow and a concise link from consumer guidance. Explain
|
||||
how maintainers retain records and deliberately address absence or conflicts.
|
||||
Keep the distinction between source publication and hosted delivery explicit.
|
||||
Document recovery limits for older attempts that lack sufficient saved evidence.
|
||||
During publication, print a copyable recovery command when a saved record is
|
||||
available and recovery is relevant. Include the explicit record path, with
|
||||
appropriate argument quoting, and preserve credential-safe output. Select the
|
||||
record supplied by the caller rather than inferring the latest attempt.
|
||||
|
||||
Add a local, read-only attempt listing for the default or explicitly selected
|
||||
storage location. Show each record's path, module, full tag, candidate commit,
|
||||
and recording time when available, with credentials redacted. Record the time
|
||||
as discovery metadata; it does not establish whether a push occurred. Listing
|
||||
reads saved records without querying remotes or choosing an attempt for recovery.
|
||||
Include discovery in the recovery guide for interrupted processes and lost output.
|
||||
Return readable attempts alongside per-record diagnostics for unreadable,
|
||||
malformed, or unsupported-format records. Identify the affected paths and retain
|
||||
credential redaction. Expose entries and diagnostics to Go callers and structured
|
||||
output; the CLI shows usable entries and indicates incomplete discovery. Report
|
||||
failure to enumerate the storage location as a discovery failure, not an empty
|
||||
collection. An explicitly selected unreadable or unsupported record produces its
|
||||
own error without selecting a different attempt.
|
||||
|
||||
Make unresolved diagnostics identify missing evidence, unreadable records,
|
||||
endpoint rewriting, and failed remote observation distinctly. Identify the
|
||||
specific missing information or configuration issue when known and explain the
|
||||
next useful operator action. For conflicts, show expected and observed tag-object
|
||||
IDs. Preserve cause information without guessing whether a transport failure is
|
||||
an authentication or connectivity problem. Apply these diagnostics to human and
|
||||
structured output while leaving corrective actions explicit.
|
||||
|
||||
### 5. Verify recovery behavior and integration
|
||||
|
||||
**Rationale:** Incorrect recovery can direct corrective action at the wrong
|
||||
repository or mistake a different annotated tag for the intended release.
|
||||
|
||||
**Target:** Behavioral coverage establishes equivalent immediate/later
|
||||
classification and useful recovery across process boundaries.
|
||||
|
||||
**Work:** Use temporary Git repositories and local remotes to cover exact matches,
|
||||
absence, different objects at the same commit, root and nested tags, changed
|
||||
current remotes, rewriting that redirects the captured endpoint, inconsistent
|
||||
observations, unavailable remotes, and cancellation. Test record round trips,
|
||||
partial evidence, default and overridden storage paths, storage failures,
|
||||
private storage behavior, and redaction with synthetic credentials. Cover automatic
|
||||
recording through both API and CLI publication, direct typed-evidence recovery,
|
||||
and explicit selection among records for multiple modules and retries. Verify
|
||||
that recording failure remains separate from publication outcome and that the
|
||||
printed recovery command selects its original record. Exercise
|
||||
recording before a failed or interrupted push and a later
|
||||
invocation after local branch or tag state changes. Verify the diagnostic
|
||||
operation performs no publication or cleanup actions and runs no candidate tests.
|
||||
Reuse existing tests for unchanged Git behavior rather than duplicating them.
|
||||
Verify that publication and record loading yield evidence directly usable by the
|
||||
Go reconciliation API. Cover discovery across modules and retries, default and
|
||||
overridden storage locations, partial records, and credential redaction. Verify
|
||||
that listing performs no remote observation or automatic attempt selection.
|
||||
Cover the diagnostic distinctions and expected/observed conflict identities
|
||||
without requiring incidental error wording.
|
||||
Cover current remote-specific transport settings, repaired configuration,
|
||||
missing or renamed remotes, and configuration-read failures. Verify that changed
|
||||
fetch or push URLs do not replace the recorded endpoint and that endpoint
|
||||
rewriting still receives the existing check. Exercise discovery containing both
|
||||
readable and damaged or unsupported records, storage-enumeration failure, and
|
||||
explicit selection of an unusable record. Preserve useful partial discovery
|
||||
results and credential-safe diagnostics across the API and CLI.
|
||||
|
||||
Run the normal offline suite and facade checks when code lands. Update GoDoc,
|
||||
CLI documentation, the publication guide, and recovery instructions together.
|
||||
Any release-policy clarification should describe later optional recovery without
|
||||
adding checks to successful publication. This roadmap itself changes no code.
|
||||
|
||||
## Resolved decisions
|
||||
|
||||
### 1. Automatic storage with an optional path override
|
||||
|
||||
Retain records automatically in private repository-local Git metadata by default,
|
||||
outside the tracked worktree, with a distinct record for each attempt. The
|
||||
standard location should serve nearly all consumers without configuration.
|
||||
Provide an optional storage-path override for consumers with particular needs.
|
||||
Apply the same record handling and privacy behavior at either location; exact
|
||||
configuration syntax belongs in interface design.
|
||||
|
||||
Automatic recording belongs in the shared publication path for both Go API and
|
||||
CLI callers. Publication results expose the saved record location and any
|
||||
recording failure separately from the publication outcome. The library returns
|
||||
this information and the CLI presents it.
|
||||
|
||||
Report the record path and require explicit selection for stored-record
|
||||
reconciliation. Record evidence before the push once the necessary identity
|
||||
exists. Treat storage failure as a clearly reported loss of the saved handoff,
|
||||
while preserving the actual publication result and existing publication
|
||||
prerequisites. An override
|
||||
changes the location, not publication's response to a recording failure.
|
||||
|
||||
Automatic storage removes a routine setup decision and keeps default records
|
||||
out of commits. Records accumulate locally, and recovery evidence can be lost
|
||||
when storage fails or its location is removed. Document retention and manual
|
||||
removal, including the sensitive nature of records at overridden locations.
|
||||
Automatic cleanup is not required for the initial capability.
|
||||
|
||||
### 2. Preserve the exact endpoint in private records
|
||||
|
||||
Preserve the exact captured endpoint in the private record, with owner-only
|
||||
access using platform-supported file permissions. Continue redacting endpoint
|
||||
credentials in human and machine diagnostic output; expose only the record
|
||||
location for the handoff. Prefer locally supplied Git credentials in adoption
|
||||
guidance. Treat exports and copies of the record as potentially sensitive,
|
||||
just like the existing API result's endpoint.
|
||||
|
||||
This supports existing publication configurations without reconstructing an
|
||||
endpoint from its display. It also places potentially sensitive material on disk;
|
||||
permissions and clear documentation reduce accidental exposure but do not promise
|
||||
encrypted storage or protection from other processes running as the same user.
|
||||
|
||||
### 3. Human-readable and structured outcomes
|
||||
|
||||
Offer human-readable output by default and a structured mode with redacted
|
||||
identity, reconciliation state, and cause information. Use success for confirmed
|
||||
publication, a non-success classification for absence/conflict, and distinct
|
||||
unknown and invocation-error classifications, following the existing CLI
|
||||
conventions where practical. Define exact numeric codes during CLI design.
|
||||
|
||||
This makes shell recovery straightforward and keeps structured state authoritative
|
||||
for detailed consumers. A successful observation of absence still yields a
|
||||
non-success process status; document that distinction explicitly.
|
||||
|
||||
### 4. Explicit attempt selection
|
||||
|
||||
Reconciliation of a stored attempt requires an explicit record path. This keeps
|
||||
selection unambiguous across modules, retries, and changed repository state.
|
||||
When a record is available and recovery is relevant, publication prints a
|
||||
copyable command selecting that record. Automatic selection of the latest record
|
||||
is outside this workflow.
|
||||
|
||||
Go callers may instead reconcile sufficient retained typed evidence directly.
|
||||
Explicit stored-record selection does not make filesystem storage a prerequisite
|
||||
for the observation API.
|
||||
|
||||
### 5. Recovery ergonomics
|
||||
|
||||
Provide local attempt discovery to recover the record path after an interrupted
|
||||
process or lost publication output. Listing exposes useful, credential-safe
|
||||
identity and recording metadata while preserving explicit selection.
|
||||
|
||||
Publication returns one reusable typed evidence value. Reconciliation accepts
|
||||
that value directly, and the supported record loader returns the same type.
|
||||
These interfaces keep evidence transfer and serialization mechanics in ReleaseKit.
|
||||
|
||||
Diagnostics distinguish missing evidence, unreadable records, endpoint rewriting,
|
||||
and failed remote observation, and identify useful next actions when known.
|
||||
Conflict output includes expected and observed tag-object IDs. This makes
|
||||
recovery actionable while preserving the existing outcome classifications and
|
||||
explicit corrective-action workflow.
|
||||
|
||||
### 6. Current transport settings with a fixed recorded endpoint
|
||||
|
||||
Record the original remote name as optional transport context. During later
|
||||
reconciliation, read its current supported transport settings and use them with
|
||||
the exact recorded endpoint. The remote's current fetch and push URLs do not
|
||||
replace that endpoint. Retain the existing check that URL rewriting leaves the
|
||||
recorded endpoint unchanged.
|
||||
|
||||
A missing or renamed remote alone does not invalidate the evidence. Observation
|
||||
can use available endpoint/global configuration; operators may restore required
|
||||
settings locally. Configuration-read failures and failed observations remain
|
||||
unresolved with cause information. This separates original publication identity
|
||||
from repairable connection settings without snapshotting transport configuration.
|
||||
|
||||
### 7. Discovery preserves readable attempts
|
||||
|
||||
An unreadable, malformed, or unsupported record produces a diagnostic while
|
||||
other readable attempts remain discoverable. Results expose both entries and
|
||||
per-record diagnostics, identifying affected paths with credential-safe output.
|
||||
The CLI presents usable entries and makes incomplete discovery clear.
|
||||
|
||||
Failure to enumerate storage is a discovery failure rather than an empty result.
|
||||
Explicit reconciliation of an unusable record reports that record's error and
|
||||
retains the caller's selection. Discovery and record errors do not authorize
|
||||
automatic selection or corrective actions.
|
||||
@@ -1,802 +0,0 @@
|
||||
# Publication reconciliation remediation
|
||||
|
||||
Status: complete. R01–R17 are implemented and verified. The full offline suite
|
||||
and compiled facade/dependency checks passed with Go 1.26.8 after the follow-up
|
||||
corrections. R01–R13 retain their original completion evidence; R14–R17 describe
|
||||
the subsequent stage-by-stage review of `363f7cd` and its fixes.
|
||||
|
||||
R01–R13 describe commits `4dff808` through `599ad81`, against planning baseline
|
||||
`447b42a`. Their audit evidence and defect descriptions refer to that reviewed
|
||||
baseline; each completion marker records the implemented correction. The full
|
||||
offline suite (`go test ./...`) and compiled facade/dependency checks
|
||||
(`go run ./internal/apicheck`) passed with Go 1.26.8 after that remediation.
|
||||
|
||||
The [feature roadmap](publication-reconciliation.md) defines the requested
|
||||
behavior; the [implementation plan](implementation.md) supplies the intended
|
||||
implementation. The broad package structure is in place: the root facade owns
|
||||
public evidence and recovery types, `internal/publish` owns observation,
|
||||
recording, and discovery, `internal/git` owns scoped endpoints and ref queries,
|
||||
and the CLI owns command parsing, presentation, and exit classification.
|
||||
Each finding records its reviewed defect, implemented correction, and
|
||||
verification.
|
||||
|
||||
## Review evidence
|
||||
|
||||
The full offline suite (`go test ./...`) and compiled facade/dependency checks
|
||||
(`go run ./internal/apicheck`) passed with Go 1.26.8. Additional disposable probes
|
||||
used local Git repositories, synthetic credentials, controlled Git subprocesses,
|
||||
and failing local output. They reproduced the findings below without changing
|
||||
production code or adding repository tests. Passing existing tests does not
|
||||
cover these failure combinations.
|
||||
|
||||
## R01: Preserve candidate failures and keep storage failures separate — P1
|
||||
|
||||
**Completed:** Capture and storage errors now remain separate. Focused publication
|
||||
regressions passed for invalid/missing notes, retained Git causes, saved partial
|
||||
evidence, and successful publication after storage lookup failure.
|
||||
|
||||
**Relevant code:** `internal/publish/publish.go:110`, especially the assignment
|
||||
at line 120 and the error check at line 127.
|
||||
|
||||
`candidate.Capture` can return useful repository/identity fields together with an
|
||||
error, such as a missing or invalid release note. When its repository field is
|
||||
present, the new storage initialization assigns `resolveRecordDir`'s error to
|
||||
the same `err` variable before the capture error is checked.
|
||||
|
||||
This produces two opposite defects:
|
||||
|
||||
- Successful storage resolution erases the capture error. Publication continues
|
||||
with a candidate that failed capture. The probe used a release note with the
|
||||
wrong product heading: `Check` rejected it, while `Publish` ran the product
|
||||
callback, returned `published` with a nil error, and pushed the annotated tag
|
||||
to the local remote.
|
||||
- Failed storage resolution replaces a successful capture with a publication
|
||||
failure. A controlled failure of the Git common-directory lookup returned
|
||||
`not_attempted`, ran no product callback, and returned a `capture release
|
||||
candidate` error as well as `RecordError`.
|
||||
|
||||
**Expected behavior:** A capture error stops publication while preserving useful
|
||||
partial evidence. A recording/storage error is reported separately and leaves
|
||||
the publication result governed by its existing checks and push outcome.
|
||||
|
||||
**Remediation:** Keep capture and storage errors in separate variables. Preserve
|
||||
the original capture failure across record initialization/finalization; use
|
||||
`RecordError` exclusively for recording failures. Add regressions for invalid
|
||||
and missing notes, capture-error cause preservation, and a valid candidate whose
|
||||
storage-directory lookup fails. Verify that rejected candidates run no product
|
||||
checks or tag creation, and that storage lookup failure permits an otherwise
|
||||
successful publication.
|
||||
|
||||
## R02: Validate the filename that will actually be written — P2
|
||||
|
||||
**Completed:** Each generated filename is checked before that exact path is
|
||||
created, including collision retries. Deterministic selective-ignore tests passed
|
||||
and confirm that rejected writes leave the candidate clean and existing records
|
||||
unchanged.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:68`, `record.go:85`, and
|
||||
`record.go:134`; the pre-push save in `internal/publish/publish.go`.
|
||||
|
||||
The override guard generates a random proposed filename and checks its Git
|
||||
ignore status. The creation loop then generates a different random filename
|
||||
and writes that file without checking its ignore status.
|
||||
|
||||
Filename-dependent ignore rules therefore allow the check and actual write to
|
||||
disagree. A probe using `selective/attempt-[0-7]*.json` in `.gitignore` successfully
|
||||
saved a record starting with `attempt-c...`; Git reported that actual record as
|
||||
untracked. The save returned success. The reverse mismatch can also reject a
|
||||
name even though another name the writer would choose is ignored.
|
||||
|
||||
**Expected behavior:** The storage safeguard applies to the exact path passed to
|
||||
exclusive creation. A failed storage choice leaves the handoff unavailable
|
||||
without introducing unignored candidate contents.
|
||||
|
||||
**Consequences:** During publication, saving occurs after the final clean-tree
|
||||
guard. An unchecked actual path can therefore leave the worktree dirty while
|
||||
publication succeeds, and can expose a sensitive record to a later ordinary
|
||||
`git add` operation. The already captured release tag does not itself include
|
||||
that new file.
|
||||
|
||||
**Remediation:** Generate one candidate path, validate that path, and use the
|
||||
same path for exclusive creation. Repeat that sequence if a filename collision
|
||||
requires another candidate. Add deterministic coverage with filename-dependent
|
||||
ignore rules; verify that successful writes are ignored and rejected choices
|
||||
leave the worktree clean. A naming seam can make this test deterministic rather
|
||||
than relying on random retries.
|
||||
|
||||
## R03: Allow an explicit override into Git metadata — P2
|
||||
|
||||
**Completed:** The override guard recognizes Git-discovered private and shared
|
||||
metadata directories. Recording tests passed for ordinary, linked-worktree, and
|
||||
separate-Git-directory layouts, alongside rejection of unignored worktree files.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:142`, especially the worktree
|
||||
containment and `git check-ignore` handling at lines 148–159.
|
||||
|
||||
For a conventional checkout, the default store is inside
|
||||
`.git/releasekit/publication-attempts`. Selecting that same directory explicitly
|
||||
activates the override guard. It treats the path as worktree content and demands
|
||||
an ignore-rule match, although Git metadata is already excluded from tracked
|
||||
source and ordinary status reporting.
|
||||
|
||||
The probe saved a default record successfully, then selected its parent
|
||||
directory as the explicit override. The second save failed with `record_write`
|
||||
and an assertion that the metadata path was inside the worktree and unignored.
|
||||
|
||||
**Expected behavior:** The agreed storage choices include Git metadata,
|
||||
external locations, and Git-ignored worktree locations. An override changes the
|
||||
location rather than imposing a requirement to add ignore rules for metadata.
|
||||
The rejection is an additional storage prohibition beyond the intended policy.
|
||||
|
||||
**Remediation:** Identify Git metadata through Git's directory discovery and
|
||||
recognize that it is outside candidate contents, including linked-worktree and
|
||||
separate-Git-directory layouts. Apply ordinary ignore checks to actual
|
||||
worktree-content paths. Add coverage for explicitly selecting the default store
|
||||
and other private locations in Git metadata, retaining rejection of genuine
|
||||
unignored worktree contents.
|
||||
|
||||
## R04: Restore cancellation across storage and recovery phases — P2
|
||||
|
||||
**Completed:** Storage and discovery share context-aware Git adapters; later
|
||||
observation classifies canceled tag validation and queries consistently. Tests
|
||||
passed for already canceled discovery and cancellation synchronized with active
|
||||
root/metadata lookups, ignore checks, publication storage, and tag operations.
|
||||
Focused Git, facade, and CLI recovery checks also passed; immediate failed-push
|
||||
reconciliation retains its existing bounded context.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:112` and `record.go:152`;
|
||||
`internal/publish/attempts.go:31` and `attempts.go:74`;
|
||||
`internal/publish/reconcile.go:109` and `reconcile.go:130`.
|
||||
|
||||
Cancellation behavior depends on which phase is active:
|
||||
|
||||
- `resolveRecordDir` and the ignore guard use `exec.Command` without the caller's
|
||||
context. A probe held the common-directory lookup, canceled publication, and
|
||||
observed that the call remained blocked until the controlled Git command was
|
||||
explicitly released.
|
||||
- Discovery resolves a default/relative store before checking `ctx.Err()`, and
|
||||
wraps all resolution failures as `storage_enumeration`. An already canceled
|
||||
default-store request consequently returned that kind instead of `canceled`.
|
||||
- `recordDirectoryFor` uses raw `exec.CommandContext`, which can return only a
|
||||
killed-process error when cancellation occurs during Git execution. The
|
||||
active-discovery probe returned `storage_enumeration` and
|
||||
`errors.Is(err, context.Canceled) == false`.
|
||||
- Later observation checks cancellation in some branches but not the tag-ref
|
||||
validation and `QueryTag` error branches. Canceling an active tag query
|
||||
returned `remote_observation`, even though its error chain correctly retained
|
||||
`context.Canceled`.
|
||||
|
||||
**Expected behavior:** Caller cancellation stops cancellable Git work and is
|
||||
reported as `canceled`, with the context cause retained. Discovery preserves
|
||||
already obtained entries. Immediate failed-push reconciliation retains its
|
||||
separately selected bounded recovery context.
|
||||
|
||||
**Remediation:** Consolidate the duplicated storage-directory/Git command work
|
||||
behind the shared internal Git/process boundary, passing the relevant context
|
||||
and preserving its cause. Normalize recovery errors at each failed operation
|
||||
using the known context cause, rather than assigning a phase kind first and
|
||||
losing cancellation. Keep partial evidence finalization and recording warnings
|
||||
separate from the original publication failure. Add coordinated tests for
|
||||
already canceled and actively canceled default/relative discovery, blocked
|
||||
storage Git commands, and cancellation during tag validation/query. Preserve
|
||||
the existing immediate-recovery timeout semantics.
|
||||
|
||||
## R05: Redact recovery paths and diagnostic fields at display boundaries — P2
|
||||
|
||||
**Completed:** Facade errors and CLI recovery displays now redact recognized
|
||||
HTTP credentials in paths, metadata, diagnostics, and handoff commands. Public
|
||||
error and CLI human/JSON regressions passed; raw typed causes and private record
|
||||
contents remain intact.
|
||||
|
||||
**Relevant code:** `recovery.go:53`; `cmd/releasekit/recovery.go:76`, its JSON DTO
|
||||
construction, and human path displays; `cmd/releasekit/main.go`'s record displays.
|
||||
|
||||
`PublicationRecoveryError.Error` redacts its wrapped cause but interpolates its
|
||||
own `RecordPath` directly. The CLI also copies record paths and diagnostic
|
||||
messages directly into human and JSON output. Safe evidence formatting protects
|
||||
the endpoint field, but does not provide a general safe-display boundary for
|
||||
these other strings.
|
||||
|
||||
Passing the synthetic value
|
||||
`https://audit-user:audit-secret@example.invalid/attempt.json` to
|
||||
`LoadPublicationEvidence` reproduced the issue. The nested file-read cause was
|
||||
redacted, while the facade error's path still contained `audit-secret`.
|
||||
`reconcile --record` with the same invalid local-record selection also emitted
|
||||
the synthetic credential in JSON output. This can expose a credential-bearing
|
||||
URL pasted into the wrong argument, despite the documented credential-safe
|
||||
error and diagnostic behavior.
|
||||
|
||||
**Expected behavior:** Recognized endpoint credentials are redacted throughout
|
||||
human and structured diagnostics. Raw values remain available in typed evidence,
|
||||
private records, and the filesystem arguments used for the actual operation.
|
||||
|
||||
**Remediation:** Apply the existing diagnostic/URL formatter to every relevant
|
||||
display string, including recovery-error paths and CLI path/identity/diagnostic
|
||||
fields, before formatting or JSON encoding. Keep raw operation inputs and private
|
||||
serialization intact. Add public-error and CLI human/JSON regressions covering
|
||||
synthetic credential-bearing paths, causes, and metadata, alongside the existing
|
||||
endpoint-only case. No new credential-storage policy is needed.
|
||||
|
||||
## R06: Propagate structured-output write failures — P2
|
||||
|
||||
**Completed:** Both recovery commands now report JSON encoder failures on
|
||||
standard error and return status 1. Failing-writer regressions passed alongside
|
||||
ordinary recovery status tests; observed publication state remains unchanged.
|
||||
The CLI reference records the reporting-failure status.
|
||||
|
||||
**Relevant code:** `cmd/releasekit/recovery.go:242` and its two `writeJSON`
|
||||
call sites.
|
||||
|
||||
`writeJSON` explicitly discards the error returned by `json.Encoder.Encode`.
|
||||
Command status is then chosen entirely from observation/discovery results,
|
||||
even when no structured result reaches the caller.
|
||||
|
||||
The probe enumerated an empty existing store with `--json`, directing stdout
|
||||
to `/dev/full`. It returned status 0 with no stderr diagnostic despite the
|
||||
failed write. The same helper is used by successful reconciliation.
|
||||
|
||||
**Expected behavior:** Automation can distinguish successful output delivery
|
||||
from an output failure. A local reporting failure remains separate from the
|
||||
already observed remote publication state.
|
||||
|
||||
**Remediation:** Return and handle the encoder error. Report the local output
|
||||
failure on stderr and return an appropriate operational failure status, while
|
||||
preserving the actual observation in the implementation rather than converting
|
||||
it to unknown remote state. Document that output-failure status in the CLI
|
||||
contract. Add focused failing-writer coverage for both structured commands;
|
||||
ordinary writable-output state/status behavior should stay unchanged.
|
||||
|
||||
## Integration audit evidence
|
||||
|
||||
A subsequent audit examined the interaction with existing consumer launchers,
|
||||
repository discovery, candidate cleanliness, and the maintainer runbook. The
|
||||
production code remains identical to the code covered by the full-suite/API
|
||||
checks above. A fresh focused run of the existing launcher, Woodpecker,
|
||||
publication-handoff, and lost-output recovery integration tests passed with
|
||||
Go 1.26.8. Disposable probes reproduced the additional issues below. R01–R06
|
||||
remain open and are preserved rather than duplicated here.
|
||||
|
||||
## R07: Make the recovery handoff survive a cold Go-tool invocation — P2
|
||||
|
||||
**Completed:** CLI recovery prefers the selected module's Go tool. API-only
|
||||
consumers receive privately retained alternate module/checksum files under Git
|
||||
metadata; native consumers retain the executable-path fallback. Assembled tests
|
||||
passed after cold executables disappeared and launcher files were removed,
|
||||
covering both adoption styles, relative replacements, clean candidates, and
|
||||
recovery exit statuses 0–3. CLI, consumer, and internal guidance is updated.
|
||||
|
||||
**Relevant code:** `cmd/releasekit/main.go:134` (`publicationRecoveryCommand`);
|
||||
`examples/multi-module/release.sh`; `docs/consumers/adoption.md`'s module-selected
|
||||
Go-tool workflow; `cmd/releasekit/publication_handoff_test.go`.
|
||||
|
||||
The new handoff always uses `os.Executable()` as the command to run later.
|
||||
Existing consumer guidance and the maintained launcher execute ReleaseKit
|
||||
through `go tool releasekit`. On an uncached tool build, Go 1.26.8 executes a
|
||||
temporary build executable and removes it when the Go driver exits. A warm
|
||||
invocation uses a surviving cached executable, so its behavior differs.
|
||||
|
||||
**Evidence:** A disposable tool-registered consumer used the current CLI with a
|
||||
cold tool build and a local remote that rejected the tag push. Publication
|
||||
returned status 1 and saved a complete record, but its printed recovery command
|
||||
named `/tmp/go-build.../exe/releasekit`. That executable was already absent when
|
||||
the driver returned, and copying the command returned status 127. A surviving
|
||||
CLI reconciled the same record successfully to `not_published` with status 1.
|
||||
The existing handoff test builds a persistent executable directly and therefore
|
||||
does not exercise this launch lifecycle.
|
||||
|
||||
**Expected behavior:** The copyable handoff works with supported consumer launch
|
||||
methods after the publication process has exited. Routine first use on a fresh
|
||||
machine/cache should not require reconstructing another invocation manually.
|
||||
The existing fallback guidance for a subsequently unavailable executable does
|
||||
not supply a working handoff for this ordinary cold-build case.
|
||||
|
||||
**Decision:** Prefer a module-selected Go invocation for recovery where the
|
||||
consumer setup supports it. Provide an explicit, durable recovery path for
|
||||
API-only consumers using temporary tool declarations. ReleaseKit owns the
|
||||
handoff mechanics and supports both adoption styles. This follows the existing
|
||||
tool-selection workflow and keeps routine binary lifecycle management out of
|
||||
consumer code.
|
||||
|
||||
**Remediation:** Make the recovery invocation independent of an ephemeral
|
||||
Go-tool executable using the selected module-based approach and an explicit
|
||||
API-only recovery path. Account for the maintained launcher's temporary
|
||||
`-modfile` cleanup and its support for ordinary
|
||||
package dependencies as well as registered tools; a bare `go tool` command
|
||||
cannot assume every consumer has a permanent tool declaration. Keep explicit
|
||||
record selection and distinct exit statuses. Add an assembled cold-build test
|
||||
that executes the printed command after the original Go driver and launcher
|
||||
have exited, rather than only testing a directly built CLI or a warm cache.
|
||||
|
||||
## R08: Reuse the existing repository-path semantics for record storage — P2
|
||||
|
||||
**Completed:** R04's shared resolver reuses the established Git root and
|
||||
metadata-path normalization. Added regressions passed for repository roots and
|
||||
separate metadata directories ending in whitespace, default/relative read-write
|
||||
agreement, and rejection of absolute unignored worktree stores. Existing ordinary
|
||||
and linked-worktree recording tests also passed.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:112`, especially line 117;
|
||||
`internal/publish/attempts.go:74`, especially line 82; the existing
|
||||
`internal/git/source.go:39` and `source.go:50` repository-root helpers.
|
||||
|
||||
The pre-existing root resolver removes Git's output newline and resolves
|
||||
filesystem aliases, preserving valid whitespace in the path itself. The new
|
||||
recording and discovery resolvers independently use `strings.TrimSpace`, which
|
||||
changes repository identity when a directory name ends in whitespace. Recording
|
||||
then uses that altered root both for relative overrides and for deciding whether
|
||||
an absolute override lies inside the candidate worktree.
|
||||
|
||||
**Evidence:** In a temporary checkout whose final directory component was
|
||||
`consumer `, the existing `git.RepositoryRoot` preserved the exact path and
|
||||
the existing candidate `Check` completed successfully. The new code produced
|
||||
three inconsistent results:
|
||||
|
||||
- A relative `.releasekit/attempts` override tried to use the different sibling
|
||||
path under `consumer` and failed its Git ignore check, despite the real
|
||||
`.releasekit/` location being ignored.
|
||||
- After a record was saved in the correct absolute ignored location, relative
|
||||
discovery selected the wrong sibling directory and returned a successful
|
||||
empty collection. The actual record still existed in the requested store.
|
||||
- An absolute override inside the actual worktree was misclassified as
|
||||
external because containment used the trimmed root. It successfully wrote an
|
||||
unignored record, which Git reported as untracked.
|
||||
|
||||
**Expected behavior:** Publication, discovery, relative overrides, and worktree
|
||||
containment use the same repository identity already supported by the library.
|
||||
Path data remains intact. This failure is independent of R02's filename mismatch:
|
||||
the containment decision itself uses the wrong root.
|
||||
|
||||
**Remediation:** Reuse `internal/git.RepositoryRoot` and share metadata-path
|
||||
resolution through the Git boundary, coordinating with R04 rather than adding
|
||||
another resolver. Remove output terminators without stripping path characters.
|
||||
Apply the same preservation to common-directory output. Add focused regressions
|
||||
for a valid root ending in whitespace, relative-store read/write agreement, and
|
||||
absolute unignored-path rejection. Preserve existing filesystem-alias and
|
||||
linked-worktree behavior instead of introducing new pathname restrictions.
|
||||
|
||||
## R09: Align maintainer recovery instructions with the source bootstrap — P2
|
||||
|
||||
**Completed:** Runbook discovery and reconciliation use the prepared source-built
|
||||
CLI, and the roadmap recovery link targets the current heading. Disposable local
|
||||
checks executed both snippets with no global `releasekit`, confirming discovery
|
||||
and the expected incomplete-evidence reconciliation status.
|
||||
|
||||
**Relevant code/docs:** `scripts/release:9`; `docs/release.md:104` and
|
||||
`release.md:149`; `docs/roadmap/publication-reconciliation.md:21`.
|
||||
|
||||
The existing maintainer procedure builds and executes
|
||||
`.releasekit/bin/releasekit`. It does not install an executable named
|
||||
`releasekit` on `PATH`. The new lost-output recovery instructions instead run
|
||||
`releasekit publication-attempts` and `releasekit reconcile` without identifying
|
||||
the executable that the procedure prepared.
|
||||
|
||||
**Evidence:** With Go and Git available on `PATH` and only the source-built CLI
|
||||
in `.releasekit/bin`, the newly documented discovery command returned 127
|
||||
(`releasekit: not found`). The equivalent command using
|
||||
`./.releasekit/bin/releasekit` returned 0 and listed the saved attempt. A globally
|
||||
installed CLI could also be a different version from the source-built one.
|
||||
|
||||
The recovery-section rename additionally left the feature roadmap's link to
|
||||
`docs/release.md#resolve-failures` pointing at a heading that no longer exists.
|
||||
|
||||
**Expected behavior:** The repository's executable runbook uses the CLI prepared
|
||||
by its own prerequisites, and its recovery links resolve to the current owner.
|
||||
It requires no additional global installation for lost-output recovery.
|
||||
|
||||
**Remediation:** Use the source-built executable for discovery and reconciliation
|
||||
in the maintainer runbook, consistent with its existing delivery commands.
|
||||
Retain explicit instructions for selecting another supported CLI when the
|
||||
original executable is genuinely unavailable. Repair the renamed-section link.
|
||||
Verify the recovery snippets in an environment without a global `releasekit`
|
||||
executable; keep generic consumer command examples in their consumer-facing
|
||||
documentation.
|
||||
|
||||
## R10: Preserve endpoint bytes across the private-record handoff — P2
|
||||
|
||||
**Completed:** Private records retain ordinary version-1 JSON and select a
|
||||
version-2 base64 byte representation for non-UTF-8 fields. Both versions load;
|
||||
unknown fields remain ignorable and malformed known byte fields report format
|
||||
errors. Round-trip tests passed for all evidence fields, and real Git
|
||||
observations distinguish the original byte path from a replacement-character
|
||||
decoy consistently before and after loading. The record-format guide is updated.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:77` and its evidence decoding;
|
||||
the string fields in `internal/publish/reconcile.go`'s `Evidence`; the existing
|
||||
Git endpoint resolution/query path in `internal/git/remote.go` and `tag.go`.
|
||||
|
||||
On POSIX, the existing Git transport can use local repository paths containing
|
||||
bytes that are not valid UTF-8. Typed evidence retains those bytes in its Go
|
||||
strings. The new private JSON codec uses `json.Marshal` directly on those
|
||||
strings, which replaces invalid UTF-8 with the Unicode replacement character
|
||||
without reporting an error. Loading the record therefore supplies a different
|
||||
endpoint to the existing Git observer.
|
||||
|
||||
**Evidence:** A disposable valid candidate's publication attempt targeted a local
|
||||
repository whose path contained a non-UTF-8 byte. Its hook rejected the push;
|
||||
publication returned `not_published` with a complete saved record and the exact
|
||||
original endpoint in typed evidence. A second local repository, named using the replacement-character
|
||||
form of that path, held the same intended annotated tag object. Direct typed
|
||||
reconciliation correctly returned `not_published` at the original destination.
|
||||
Loading the saved record changed its endpoint to the second repository and
|
||||
record-based reconciliation returned `published` there. The record save/load
|
||||
reported no error.
|
||||
|
||||
**Expected behavior:** Direct and record-loaded evidence identify the same
|
||||
captured destination. The private handoff retains exact transport identity,
|
||||
including pathname bytes supported by the existing Git boundary, or reports
|
||||
that the handoff could not be saved faithfully. Diagnostic JSON is a display
|
||||
format and can use safe displays; the private record supplies actual operation
|
||||
inputs and must preserve their identity.
|
||||
|
||||
**Decision:** Use a lossless private-record representation for supported
|
||||
pathname and endpoint bytes. This preserves existing publication inputs and
|
||||
keeps record-loaded recovery consistent with direct typed evidence. Recording
|
||||
failures remain separate from the publication outcome.
|
||||
|
||||
**Remediation:** Make private serialization lossless for supported evidence,
|
||||
using an explicit byte representation where needed. Keep the original typed
|
||||
evidence and publication behavior intact; avoid adding a general UTF-8
|
||||
restriction to previously valid Git publication inputs. Cover endpoint/path
|
||||
round trips and equivalent direct-versus-loaded observations, including a
|
||||
distinct replacement-character path, in focused regression tests.
|
||||
|
||||
## Algorithmic audit scope and evidence
|
||||
|
||||
This audit retains the planned flat record store, complete local discovery,
|
||||
deterministic ordering of selected record paths, versioned JSON format, and
|
||||
exact-tag observation semantics. It does not propose indexes, pagination,
|
||||
automatic retention, or changes to those selected algorithms.
|
||||
|
||||
No unexpected quadratic algorithm was found in the added production code.
|
||||
Classification operates on a fixed number of fields and bounded-length object
|
||||
IDs. Record encoding/decoding uses a fixed number of linear passes over a
|
||||
record. Facade and CLI result conversions are linear in returned entries and
|
||||
displayed data. Full-result discovery necessarily retains the records its API
|
||||
returns. The refinements below concern work outside those necessary operations
|
||||
and one partial-result correctness defect.
|
||||
|
||||
The existing codec, discovery, and classification tests passed in a fresh
|
||||
focused run with Go 1.26.8. Complexity analysis used the implementation and
|
||||
the local Go standard-library source, without benchmarks or real-world
|
||||
performance measurements. Disposable functional probes confirmed the parser
|
||||
cause loss and partial-enumeration result loss. The latter used a temporary
|
||||
source overlay only to make the directory-reader boundary return real entries
|
||||
with a controlled error; repository production and test files were unchanged.
|
||||
|
||||
## R11: Filter directory entries before ordering them — P3
|
||||
|
||||
**Completed:** Discovery filters unsorted directory batches and sorts selected
|
||||
record paths once. Directory work is `O(D + R log R)` with bounded intermediate
|
||||
entry storage. Mixed-directory, ordering, and cancellation regressions passed.
|
||||
R13 addresses preservation of records alongside enumeration failures.
|
||||
|
||||
**Relevant code:** `internal/publish/attempts.go:41` through the subsequent
|
||||
filter and `sort.Strings(paths)`.
|
||||
|
||||
`os.ReadDir` already sorts every directory entry by filename. Discovery then
|
||||
filters for JSON record files and sorts their full paths again. Consequently,
|
||||
an overridden store containing many unrelated files pays to order names that
|
||||
will never appear in the result. This is an implementation choice beyond the
|
||||
planned requirement to order selected records.
|
||||
|
||||
**Complexity:** Let `D` be all directory entries, `R` the selected JSON file
|
||||
candidates, and `B` their total bytes read. Treating name comparisons as unit
|
||||
operations, the current directory ordering has worst-case `O(D log D)` cost
|
||||
before the same `O(B)` record work. Filtering an unsorted enumeration and sorting
|
||||
only selected paths costs `O(D + R log R + B)`. When `R` is bounded and `D`
|
||||
grows, directory processing improves from `O(D log D)` to `O(D)`. When nearly
|
||||
every entry is a record, the planned sorting bound remains unchanged. The
|
||||
second sort currently receives already ordered paths; it is redundant, rather
|
||||
than evidence of a quadratic algorithm.
|
||||
|
||||
**Expected behavior:** Keep complete discovery and deterministic path ordering
|
||||
for attempts and their diagnostics. Unrelated directory entries need no order.
|
||||
|
||||
**Remediation opportunity:** Use the standard `os.File.ReadDir` interface to
|
||||
enumerate without implicit sorting, filter candidates, and order those paths
|
||||
once. Batched reads can also reduce intermediate directory-entry storage from
|
||||
`O(D)` to `O(batch size + R)`, excluding retained result data. Preserve partial
|
||||
enumeration errors as described in R13 and cancellation behavior from R04.
|
||||
Keep this a local enumeration refinement; no public file-count limits or
|
||||
changes to record selection are required.
|
||||
|
||||
## R12: Reject trailing JSON without constructing a discarded value — P3
|
||||
|
||||
**Completed:** Loading uses complete-document unmarshalling and wraps the actual
|
||||
parser error. It avoids constructing a discarded trailing value while retaining
|
||||
the whole-file read bound. Tests passed for single documents, whitespace,
|
||||
additional fields, extra documents, malformed suffixes, and retained syntax
|
||||
causes, alongside byte-preserving record regressions.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:24`, particularly the second
|
||||
`decoder.Decode(&trailing)` and its error handling at lines 34–37.
|
||||
|
||||
After decoding the envelope, the loader decodes another complete JSON value
|
||||
into `any` solely to check whether one exists. A second value always makes the
|
||||
record invalid. Nevertheless, an appended array or object is fully parsed and
|
||||
materialized before being discarded. Only trailing whitespace and EOF are
|
||||
acceptable at that point.
|
||||
|
||||
**Complexity:** For a first envelope of `L` bytes and an appended value of `T`
|
||||
bytes containing `M` nodes, the second decode adds `Theta(T)` parsing work and
|
||||
up to `Theta(M)` object/array storage. Rejecting at the first non-whitespace byte
|
||||
after the envelope needs no second value tree. A standard-library complete-
|
||||
document unmarshal of the already loaded bytes can reject that trailing input
|
||||
during syntax validation before constructing it. This removes avoidable parsing
|
||||
and allocations; the existing whole-file read still has its `Theta(L + T)` I/O
|
||||
and buffer bound. It is not an overall quadratic-to-linear claim.
|
||||
|
||||
There is also a correctness inconsistency: a non-EOF error from that second
|
||||
decode is replaced by a new message without wrapping the original error.
|
||||
Functional probes showed that an incomplete first document retained
|
||||
`io.ErrUnexpectedEOF`, while an incomplete trailing document lost it. A syntax
|
||||
error in the first document was available through `errors.As` as
|
||||
`*json.SyntaxError`; the corresponding trailing syntax error was not.
|
||||
|
||||
**Expected behavior:** Require one complete document, retain supported partial
|
||||
evidence/additional-field behavior, and preserve available parsing causes in
|
||||
the typed `record_format` error. Invalid extra data needs no materialized tree.
|
||||
|
||||
**Remediation opportunity:** Simplify loading to a complete-document unmarshal
|
||||
into the versioned envelope, retaining existing format/evidence validation and
|
||||
cause wrapping. An equivalent trailing-data check can also work if it avoids
|
||||
decoding an arbitrary second value and retains real parser failures. Add focused
|
||||
single-document, extra-document, trailing-whitespace, and malformed-suffix
|
||||
coverage. Keep raw record identity handling coordinated with R10; no new size
|
||||
caps or public input restrictions are needed.
|
||||
|
||||
## R13: Retain entries returned with a directory-enumeration error — P2
|
||||
|
||||
**Completed:** Discovery loads returned record paths before reporting enumeration
|
||||
failure. Fault-injected entries-plus-error and zero-entry tests passed, preserving
|
||||
readable records, per-record diagnostics, ordering, and the original cause.
|
||||
Facade conversion and CLI human/JSON tests confirm partial results survive with
|
||||
a non-success status. The full offline suite and API/dependency checks passed.
|
||||
|
||||
**Relevant code:** `internal/publish/attempts.go:41` through its immediate error
|
||||
return at lines 42–44; Stage 5's partial-result requirement in the implementation
|
||||
plan.
|
||||
|
||||
Go's `os.ReadDir` can return both entries read successfully and a later read
|
||||
error. Discovery checks the error first and immediately returns its still-empty
|
||||
result, dropping every returned entry. Thus a filesystem enumeration failure
|
||||
can hide readable attempts that were already discovered, even though the API
|
||||
and CLI support returning attempts alongside a top-level failure.
|
||||
|
||||
**Evidence:** A disposable probe supplied the directory-reader boundary with
|
||||
the real entry for one valid record plus a controlled enumeration error, an
|
||||
outcome permitted by `os.ReadDir`'s contract. `ListPublicationAttempts` returned
|
||||
zero attempts with `storage_enumeration`. Without the injected error, it
|
||||
returned that same record successfully. This was fault injection at the read
|
||||
boundary, not a change to the discovery algorithm under review.
|
||||
|
||||
**Expected behavior:** Preserve readable attempts and per-record diagnostics
|
||||
from returned entries while reporting the top-level enumeration failure. The
|
||||
operation remains incomplete and retains its non-success CLI classification.
|
||||
|
||||
**Complexity:** Processing a returned prefix of `d` entries containing `r`
|
||||
record candidates and `b` readable bytes takes the same ordinary filtering,
|
||||
ordering, and decoding work: `O(d + r log r + b)` with R11's refinement. The
|
||||
existing `os.ReadDir` approach instead retains its `O(d log d + b)` bound.
|
||||
Neither correction requires another directory scan: both retain the names
|
||||
already enumerated and perform the normal record loads for those names.
|
||||
|
||||
**Remediation:** Retain the enumeration error, process available entries using
|
||||
the normal discovery path, and return both the collected result and that error.
|
||||
Handle zero-entry failures and cancellation consistently with R04. Add a narrow
|
||||
fault-injected regression for entries-plus-error, verifying readable attempts,
|
||||
retained causes, and API/CLI partial-result behavior. This concerns data
|
||||
preservation, separate from R04's cancellation/error classification.
|
||||
|
||||
## Follow-up stage review evidence
|
||||
|
||||
The subsequent review examined all eight completed stages in the implementation
|
||||
plan, including the R01–R13 corrections, at `363f7cd`. Existing tests for
|
||||
classification, Git endpoints, records, public recovery, publication evidence,
|
||||
discovery, CLI parsing/presentation, cold Go-tool handoffs, and the assembled
|
||||
lost-output workflow passed with Go 1.26.8. The compiled facade/dependency checks
|
||||
and local documentation link/fragment checks also passed.
|
||||
|
||||
Disposable local probes reproduced R14–R17 using temporary Git repositories and
|
||||
Go source overlays. Repository production code and tests remained unchanged.
|
||||
No additional defects were identified in Stages 3–6. Stage 8 needs regression
|
||||
coverage for the cases below and plan wording aligned with the implemented
|
||||
record and handoff decisions. Existing passing coverage does not establish
|
||||
correctness for these additional combinations.
|
||||
|
||||
## R14: Support recovery for vendored API-only consumers — P2
|
||||
|
||||
**Completed:** API-only recovery uses a private module root with preserved module
|
||||
settings and relocated local replacements. Vendored API-only, relative/versioned
|
||||
replacement, cold Go-tool, native handoff, and source-module entry-point tests
|
||||
passed. The assembled vendored case also runs publication through a private
|
||||
Go-tool launcher, removes its metadata, and executes the later handoff from
|
||||
another directory. Stage 7/8 instructions and consumer/internal guidance are
|
||||
updated.
|
||||
|
||||
**Relevant code:** `internal/gotool/recovery.go:95–107`; the command assembly in
|
||||
`cmd/releasekit/main.go:126–145`. Affects Stage 7 and its Stage 8 coverage.
|
||||
|
||||
The API-only handoff prepares its private alternate module files with
|
||||
`go list -mod=mod`, then prints a `go tool` command running from the consumer's
|
||||
module directory. Go automatically selects vendor mode when that directory
|
||||
contains a suitable `vendor` tree. An API-only consumer vendors its imported
|
||||
library packages, so the CLI package can be absent from that tree.
|
||||
|
||||
**Evidence:** A temporary consumer imported ReleaseKit through its Go API,
|
||||
vendored its dependencies, and used the current CLI for a locally rejected
|
||||
publication. A complete record and recovery command were produced. Executing
|
||||
the command failed before reconciliation with `import lookup disabled by
|
||||
-mod=vendor`. Supplying `GOFLAGS=-mod=mod` also failed to change this Go-tool
|
||||
behavior. Existing vendored-tool coverage uses an explicitly registered and
|
||||
vendored CLI, which does not exercise this API-only case.
|
||||
|
||||
**Expected behavior:** A consumer's ordinary library vendoring remains compatible
|
||||
with the supported API-only recovery handoff. The command observes the explicitly
|
||||
selected record using the selected ReleaseKit dependency.
|
||||
|
||||
**Remediation:** Prepare the API-only Go-tool entry point in a private bootstrap
|
||||
module root whose tool resolution is independent of the consumer's vendor tree.
|
||||
Preserve the selected dependency graph, checksums, versioned replacements, and
|
||||
the meaning of relative local replacements when relocating module metadata.
|
||||
Keep source files and consumer vendoring intact, retain private storage handling,
|
||||
and keep bootstrap ownership within ReleaseKit. Preserve registered-tool and
|
||||
native entry points. Update Stage 7's handoff instructions to describe this
|
||||
module-selected approach and the supported native fallback.
|
||||
|
||||
**Regression coverage:** Add an assembled API-only consumer that vendors the
|
||||
library without a tool declaration. Execute the printed command from another
|
||||
directory after publication and launcher cleanup; assert that reconciliation
|
||||
runs against the saved identity and returns its expected state/status. Cover
|
||||
relative local replacements and unchanged consumer source/vendor contents.
|
||||
Retain existing cold-build, registered/vendored-tool, quoting, and status cases.
|
||||
|
||||
## R15: Preserve the recovery handoff after publication cancellation — P2
|
||||
|
||||
**Completed:** Post-failure handoff preparation has a separate bounded context
|
||||
and retains a persistent native fallback after module-preparation failure.
|
||||
Coordinated native, cold registered-tool, and temporary-launcher cancellation
|
||||
tests passed, including execution of the printed command after cleanup and
|
||||
retained cancellation diagnostics/statuses. Temporary Go-work executable
|
||||
classification is covered, and Stage 7/8 plus canonical guidance is updated.
|
||||
|
||||
**Relevant code:** `cmd/releasekit/main.go:116–134`, especially the caller context
|
||||
passed to `git.CommonDirectory`. Affects Stage 7 and its Stage 8 coverage.
|
||||
|
||||
Command preparation reuses the publication context after publication returns.
|
||||
When cancellation caused the failure, that context is already canceled. The
|
||||
metadata lookup fails immediately and the CLI prints `recovery command
|
||||
unavailable`, even when it has saved the record and a usable native entry point
|
||||
is available. The native fallback is reached only when module selection returns
|
||||
no Go invocation; a preparation error bypasses it.
|
||||
|
||||
**Evidence:** A probe synchronized cancellation with a blocked tag push. The
|
||||
publication returned status 1 after immediate reconciliation found the tag
|
||||
absent. Standard error reported a saved record and an unavailable recovery
|
||||
command, with no copyable handoff. This was cancellation during execution,
|
||||
rather than termination before the record could be saved.
|
||||
|
||||
**Expected behavior:** An ordinary canceled publication with a saved record
|
||||
retains a usable recovery entry point. Its preparation leaves the original
|
||||
publication outcome, cause, and exit classification intact.
|
||||
|
||||
**Remediation:** Arrange handoff preparation so the failed publication's canceled
|
||||
context does not prevent command construction. Prepare necessary durable launch
|
||||
metadata before cancellation-sensitive publication work where appropriate, and
|
||||
retain a usable native-command fallback when module-based preparation fails.
|
||||
Account for cold Go-tool executables that disappear when the original driver
|
||||
exits. Keep publication and later observation cancellation behavior from R04;
|
||||
any separate post-failure preparation that performs subprocess work needs a
|
||||
bounded lifetime. Update Stage 7 to cover cancellation and preparation failures.
|
||||
|
||||
**Regression coverage:** Cancel an actively blocked push after pre-push recording,
|
||||
then execute the resulting command using a fresh invocation/context. Cover a
|
||||
persistent native executable and supported cold Go-tool adoption paths after
|
||||
launcher cleanup. Assert saved identity selection, unchanged publication status
|
||||
and original cancellation cause, and preservation of immediate recovery's bounded
|
||||
context. Coordinate the push boundary explicitly and retain the existing
|
||||
no-recovery-step behavior for successful publication.
|
||||
|
||||
## R16: Classify endpoint setup failures by their actual cause — P3
|
||||
|
||||
**Completed:** The Git boundary marks configuration-read/resolution failures;
|
||||
observation maps those, positive rewrites, and other operational errors to the
|
||||
existing kinds while preserving causes. Named/unnamed malformed configuration,
|
||||
rewrite, tooling, cancellation, facade, and CLI diagnostic tests passed. Tests
|
||||
also verify no tag query after setup failure; Stage 1/6/8 guidance is updated.
|
||||
|
||||
**Relevant code:** `internal/publish/reconcile.go:116–128` and
|
||||
`internal/git/remote.go`'s endpoint setup. Affects Stages 1 and 6.
|
||||
|
||||
An `ObserveEndpoint` error defaults to `endpoint_changed` when the evidence has
|
||||
no original remote name. With a name, the fallback is `transport_configuration`.
|
||||
The choice can therefore describe the presence of optional context rather than
|
||||
the failure that occurred.
|
||||
|
||||
**Evidence:** A local checkout with malformed Git configuration and evidence
|
||||
whose `Remote` was empty returned `unknown` with `endpoint_changed`. Its retained
|
||||
cause was Git's bad-configuration error; no changed destination had been
|
||||
established. CLI guidance consequently points the operator toward URL rewrite
|
||||
settings despite the configuration-read failure.
|
||||
|
||||
**Expected behavior:** `endpoint_changed` identifies a positively detected
|
||||
destination change. Configuration and other operational setup failures retain
|
||||
an appropriate unresolved classification and their original causes, including
|
||||
when the original remote name is absent.
|
||||
|
||||
**Remediation:** Distinguish setup failures at the internal Git boundary where
|
||||
their source is known, then map them to the supported recovery kinds. Use the
|
||||
existing endpoint-change sentinel for positively detected rewrites. Preserve
|
||||
configuration/tooling causes through the facade and use the same interpretation
|
||||
for human guidance and structured diagnostics. Clarify this mapping in Stage 1
|
||||
and its Stage 6 presentation requirements using the existing recovery kinds.
|
||||
|
||||
**Regression coverage:** Exercise malformed local configuration with an empty
|
||||
original remote name, a genuine rewrite mismatch, and a separate operational
|
||||
setup failure at the Git boundary. Verify typed kinds, retained causes, unknown
|
||||
state, and corresponding CLI human/JSON diagnostics. Keep remote queries from
|
||||
reaching a substituted endpoint when a rewrite mismatch is detected.
|
||||
|
||||
## R17: Validate the documented version-2 byte-field representation — P3
|
||||
|
||||
**Completed:** Known byte fields load through explicit JSON string validation and
|
||||
standard base64 decoding. Tests passed for valid/empty strings, numeric arrays,
|
||||
other structural types, malformed base64, and retained decoding causes, alongside
|
||||
existing partial/unknown-field, version-1, and lossless endpoint regressions.
|
||||
Stage 2/8 instructions and the canonical format guide are updated.
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:48–59`;
|
||||
[private record format](../integrations/publication-records.md#private-attempt-record).
|
||||
Affects Stage 2 and its Stage 8 format coverage.
|
||||
|
||||
Version-2 byte fields are documented as standard base64 strings. The loader
|
||||
unmarshals each known field directly into `[]byte`; Go's JSON decoder also accepts
|
||||
numeric arrays for that destination type. Consequently, an invalid structural
|
||||
type under the documented format is silently treated as usable evidence.
|
||||
|
||||
**Evidence:** Both `"djEuMi4z"` and `[118,49,46,50,46,51]` as the value of
|
||||
`evidence_bytes.tag` loaded as `v1.2.3` without a format error. The array case is
|
||||
outside the declared representation. This is a format-validation gap; the probe
|
||||
did not demonstrate altered bytes or an incorrect remote observation.
|
||||
|
||||
**Expected behavior:** Known byte fields follow the documented base64-string
|
||||
representation. Structural and encoding failures return `record_format` with
|
||||
their available causes. Supported partial evidence and ignored additional fields
|
||||
retain their existing behavior.
|
||||
|
||||
**Remediation:** Decode known byte-field values as strings explicitly, then use
|
||||
the standard base64 decoder to recover their bytes, preserving decoding causes.
|
||||
Keep version-1 behavior and version-2 lossless identity from R10. Update Stage 2
|
||||
to describe both supported versions and the writer's version selection, linking
|
||||
to the canonical format definition.
|
||||
|
||||
**Regression coverage:** Add known byte-field cases for valid base64, malformed
|
||||
base64, numeric arrays, and other invalid structural types. Assert typed format
|
||||
errors and retained causes for invalid inputs. Reuse the existing all-field
|
||||
round trips, non-UTF-8 endpoint observation, partial-evidence, and unknown-field
|
||||
tests rather than duplicating their fixtures.
|
||||
|
||||
## Implementation plan and verification follow-through
|
||||
|
||||
**Completed:** [implementation.md](implementation.md) records the R14–R17
|
||||
corrections alongside their implementation:
|
||||
|
||||
- Stage 1 records the cause-based endpoint setup classification from R16.
|
||||
- Stage 2 describes ordinary version-1 records and version-2 byte-preserving
|
||||
records, including the structural validation from R17.
|
||||
- Stage 6 aligns typed failures, human guidance, and structured diagnostics with
|
||||
R16 and retains the implemented JSON output-failure handling from R06.
|
||||
- Stage 7 replaces the original executable-only handoff instructions with R07's
|
||||
module-selected/durable API-only approach, extended for vendoring and
|
||||
cancellation by R14 and R15, including native fallback and explicit record
|
||||
selection.
|
||||
- Stage 8 includes the regression coverage assigned above and updated canonical
|
||||
documentation. Final assembly passed `go test ./...`,
|
||||
`go run ./internal/apicheck`, and `git diff --check`; local links and fragment
|
||||
destinations were checked. Validation used Go 1.26.8 with offline dependencies.
|
||||
|
||||
Earlier completion evidence and the distinction between original and follow-up
|
||||
corrections are retained. Each issue has a completion marker. The corrections
|
||||
retain the existing public facade, publication completion point, separate
|
||||
recording failures, and operator-owned recovery choices; they introduce no
|
||||
consumer pilot, intermediate release, or external review gate.
|
||||
Reference in New Issue
Block a user