From 1e69abc88cf748537cd7febc85e0743755fa2fc1 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 2 Nov 2026 00:07:18 +0000 Subject: [PATCH] Adopt ReleaseKit and add hosted release delivery --- .gitignore | 3 ++ docs/development.md | 3 +- docs/policy/oss.md | 5 ++- docs/policy/release.md | 26 ++++++++---- docs/release.md | 81 ++++++++++++++++++++++++++++++++++---- docs/releases/v0.6.0.md | 2 +- go.mod | 4 +- go.sum | 4 +- releasekit.json | 8 ++++ releasekit_profile_test.go | 57 --------------------------- 10 files changed, 112 insertions(+), 81 deletions(-) delete mode 100644 releasekit_profile_test.go diff --git a/.gitignore b/.gitignore index 7f77e4f..545f84b 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ go.work.sum # Local codebase-memory tooling artifacts /.codebase-memory/ +# Local release credentials and tooling +/.releasekit/ + # ---> Linux *~ diff --git a/docs/development.md b/docs/development.md index 9cad2ce..846f622 100644 --- a/docs/development.md +++ b/docs/development.md @@ -113,7 +113,7 @@ git show 8e91a2b:docs/roadmap/startup-benchmark.md ``` Remaining optimization candidates belong to -[future performance experiments](roadmap/future.md#5-performance-experiments). +[future performance experiments](roadmap/future.md#3-performance-experiments). The completed effective-configuration diff roadmap, implementation plan, and resolved review findings are preserved in Git history at `1c2610c` under @@ -294,7 +294,6 @@ code changes. Use the component overview to find the corresponding implementatio | Inheritance scaling evidence | [Expansion benchmarks](../internal/engine/inheritance_bench_test.go) | `go test ./internal/engine -run '^$' -bench '^BenchmarkInheritance' -benchmem` | | Documentation links and dependency isolation | [Link checks](../documentation_test.go), [import boundary](../import_boundary_test.go), [public boundary](../public_boundary_test.go) | `go test . -run 'TestLocalDocumentationLinks\|TestDependencyImportBoundary\|TestPublicFacadeBoundary'` | | Development check behavior | [Development checker test](../development_check_test.go) | `go test . -run '^TestDevelopmentCheckHandlesFilenamesAndStopsOnFailure$'` | -| Release profile wiring | [Release profile test](../releasekit_profile_test.go) | `go test . -run '^TestReleaseKitProfile$'` | ## Fuzz validation diff --git a/docs/policy/oss.md b/docs/policy/oss.md index baf4d46..ef2981e 100644 --- a/docs/policy/oss.md +++ b/docs/policy/oss.md @@ -11,8 +11,9 @@ ConfigKit's selected grant is LGPL-3.0-or-later, recorded in [COPYRIGHT](../../COPYRIGHT), with the complete LGPLv3 and incorporated GPLv3 texts in [LICENSE](../../LICENSE). The [release procedure](../release.md) -owns source-only Go module publication through Gitea. No separate hosted release -object or license asset is required for this channel. +owns source-only Go module publication and, beginning with v1.0.0, local delivery +of a Gitea release page using the complete committed release note. That page +retains the note's licensing footer; separate license assets are not required. ## Purpose And Scope diff --git a/docs/policy/release.md b/docs/policy/release.md index 3f9434e..80a9966 100644 --- a/docs/policy/release.md +++ b/docs/policy/release.md @@ -23,9 +23,10 @@ policy and link to it rather than introduce additional release requirements. ConfigKit publishes a source-only Go module. Its declared deliverables are the immutable annotated source tag and version-matched checked-in release note. -Standalone binaries, container images, hosted release objects, and separate -registry publication are not required. Additional channels require a deliberate -update to the policy and procedure. +Beginning with v1.0.0, maintainers also deliver a Gitea release page containing +that complete note after source publication. Standalone binaries, container +images, and separate registry publication are not required. Further channels +require a deliberate update to the policy and procedure. The Go API, documented YAML and resolution semantics, and structured diagnostic contracts participate in compatibility review. A signature-preserving change @@ -80,9 +81,13 @@ the tagged source. The procedure must identify those deliverables and the responsibility of their automation. Published deliverables must be traceable to that source and identify the same version. -ConfigKit currently declares no additional deliverables. Confirmed publication -of the intended source tag is the completion point. CI, macOS validation, -hosted delivery, and consumer retrieval are outside the release gates. +ConfigKit's hosted release page is delivered locally through ReleaseKit after +confirmed source publication, using the tagged note and preserving its licensing +footer. Hosted delivery has a separately reported outcome and may be retried +against the same immutable tag without republishing it. Confirmed publication +of the intended source tag remains the source-release completion point. CI, +macOS validation, hosted-service availability, delivery credentials, and consumer +retrieval are outside the source-publication gates. ## Versioning And Compatibility @@ -176,8 +181,8 @@ ConfigKit uses the complete checked-in note as the annotated tag message. Include the release footer defined by the [OSS policy](oss.md#discoverability) in each new release document. The release procedure defines the concrete URLs and publication mechanics. Prepare these links with the release note; they do -not add a network check or approval gate. If a hosted download page is used in -a future channel, preserve the footer there. +not add a network check or approval gate. ConfigKit's hosted release page reuses +the complete committed note and its footer. Handle version selection, release-note content, and retirement of completed roadmaps during preparation. Preserve planned and deferred work and repair @@ -221,6 +226,11 @@ expanded platform matrices solely because they are possible. Retain such checks when their distinct protection justifies their cost. A one-time migration check must not silently become a permanent release requirement. +ConfigKit selects ReleaseKit's targeted forbidden-file check to keep the local +delivery token path out of the committed candidate. The profile owns the path; +the check does not inspect file contents or Git history. Hosted credential +preflight belongs to delivery and adds no source-publication gate. + ReleaseKit runs its selected Go commands with readonly module mode and `GOPROXY=off`; acquire dependencies and build the tool before candidate validation. Formatting and whitespace remain ordinary development checks in diff --git a/docs/release.md b/docs/release.md index 9e2e25d..87e7f4a 100644 --- a/docs/release.md +++ b/docs/release.md @@ -5,7 +5,9 @@ A release publishes the Go source under a new Git tag, with a matching release note committed in the repository. The tag is annotated: it includes the release note text and points to the released commit. Published tags and contents are immutable. There is no binary, container image, generated package, or separate -registry publication. +registry publication. Beginning with v1.0.0, maintainers also deliver a Gitea +release page containing the complete checked-in note after source publication. +Source publication and hosted delivery have separate outcomes. A **candidate** is the committed revision you intend to release. The **authoritative remote** is the repository where ConfigKit publishes releases; @@ -99,17 +101,27 @@ commands never install another ReleaseKit version: ```sh releasekit_tmp=$(mktemp -d "${TMPDIR:-/tmp}/configkit-releasekit.XXXXXX") releasekit_bin="$releasekit_tmp/releasekit" -go build -o "$releasekit_bin" gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit -go list -m gitea.maximumdirect.net/eric/ReleaseKit +go build -o "$releasekit_bin" go.maximumdirect.net/releasekit/cmd/releasekit +go list -m go.maximumdirect.net/releasekit "$releasekit_bin" version ``` -This repository selects ReleaseKit v0.4.2. Use the compiled program above for +This repository selects ReleaseKit v0.6.0. Use the compiled program above for publication so its exit status and publication report reach you unchanged. `go run` maps a nonzero child exit status to 1. ReleaseKit's child Go commands use the `go` found on `PATH` with `GOTOOLCHAIN=local`; keep the selected executable first on `PATH` through the publication command. +For hosted delivery, save the Gitea API token in the ignored local file +`.releasekit/gitea-token` with owner-only permissions. The profile uses it when +`RELEASEKIT_GITEA_TOKEN` is unset or blank; a nonblank environment value takes +precedence. The file path resolves from the repository root, and ReleaseKit +trims surrounding whitespace from its contents. Keep credentials out of release +notes, diagnostic labels, command arguments, and commits. The selected +`forbidden_files` check also rejects a committed token at that exact path; +it does not scan other files or history. Source publication uses Git +authentication and does not read or require this token. + Verify access to the authoritative remote without displaying credentials: ```sh @@ -188,7 +200,7 @@ For `publish`, interpret the program's exit status as follows: | Status | Meaning | Next action | | --- | --- | --- | -| `0` | Publication succeeded. | The release is complete. Optional verification can follow. | +| `0` | Publication succeeded. | The source release is complete. Continue with hosted delivery. | | `1` | A known publication failure or conflict was reported. | Read the report and correct the stated problem. Preserve any published tag. | | `2` | The command, profile, or version is invalid. | Correct the command or configuration before trying again. | | `3` | The tag push outcome is still unknown. | Follow the recovery procedure below before retrying or declaring success. | @@ -200,7 +212,62 @@ The command does not push or repair `main`, wait for CI, or publish binaries or a hosted release page. A successful intended-tag publication does not require remote read-back or consumer retrieval; those checks remain optional. -## 5. Recover from a failed or uncertain tag push +## 5. Deliver the hosted release page + +After source publication succeeds, run local hosted delivery from the repository +root. Keep the worktree clean and `HEAD` at the published tag's commit. Reuse the +compiled executable from step 1. For the first release using this workflow: + +```sh +"$releasekit_bin" deliver v1.0.0 +``` + +For subsequent releases, substitute the selected version. The profile selects +Gitea at `https://forge.maximumdirect.net`, owner `base-layer`, repository +`configkit`. With no build targets, delivery creates or accepts a stable, +non-draft release page containing the complete committed note, including its +licensing footer. It creates no binary assets, receipts, or checksum manifest. +An existing page with matching content is accepted; conflicting content is not +overwritten. + +Delivery automatically performs an authenticated, read-only repository preflight +before hosted writes. A separate preflight before source publication is not a +release gate. Delivery does not rerun candidate validation, publish a tag, or +wait for CI. Hosted-service availability and delivery credentials do not gate +source publication. Report source-publication success and hosted-delivery status +separately. + +| Delivery status | Meaning and next action | +| --- | --- | +| `0` | Delivery is complete; report the hosted release URL. | +| `1` | Preflight was unavailable, or delivery failed, conflicted, or made partial progress. Read the reported state and correct the cause; investigate conflicts before retrying. | +| `2` | Usage, profile, credential loading, or preflight configuration/access failed. Correct the reported problem before retrying delivery. | +| `3` | A hosted write has an unresolved outcome. Inspect the hosted release and resolve that state before retrying. | + +A delivery problem leaves the successfully published source tag intact. Retry +delivery with the same immutable version after correcting the cause and +resolving any uncertain write. For a later retry, use a clean checkout of the +original tag, fetching that tag if needed. Build the module-selected CLI from +that checkout using step 1's tool-build commands, without changing the tagged +module files, and make the token available there. For example, with the tag +available locally: + +```sh +release_version=v1.0.0 +git switch --detach "$release_version" +``` + +After preparing the tool in that tagged checkout: + +```sh +"$releasekit_bin" deliver "$release_version" +``` + +Do not rerun `publish`, move a tag, or replace a hosted page to repair delivery. +If published source or content needs correction, prepare a new appropriate +version through the normal release procedure. + +## 6. Recover from a failed or uncertain tag push The executable reports the publication state, phase, version, candidate commit, tag-object ID, effective endpoint, and error on standard error. After a failed @@ -250,7 +317,7 @@ back `main`. A conflicting published tag must never be moved or reused. A defect in published source requires a corrected commit, a new semantically appropriate version, and a matching release note. -## 6. Optional consumer verification +## 7. Optional consumer verification When useful or requested, read back the remote tag or retrieve the exact version into a new module without a local `replace` directive. These checks are diff --git a/docs/releases/v0.6.0.md b/docs/releases/v0.6.0.md index a0ce871..fa9b43d 100644 --- a/docs/releases/v0.6.0.md +++ b/docs/releases/v0.6.0.md @@ -30,7 +30,7 @@ adopts ReleaseKit for source publication, and raises the consumer Go minimum to successful-tag-push completion point remain unchanged; CI is not a gate. - Adopt release policy 1.6, distinguishing the consumer Go minimum from the validation toolchain. Retire completed planning and measurement records into - Git history while preserving [performance candidates](../roadmap/future.md#5-performance-experiments). + Git history while preserving [performance candidates](../roadmap/future.md#3-performance-experiments). ## Compatibility and upgrade diff --git a/go.mod b/go.mod index e521fef..9395b78 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.26.0 require go.yaml.in/yaml/v4 v4.0.0-rc.6 require ( - gitea.maximumdirect.net/eric/ReleaseKit v0.4.2 // indirect github.com/yuin/goldmark/v2 v2.1.6 // indirect + go.maximumdirect.net/releasekit v0.6.0 // indirect golang.org/x/mod v0.40.0 // indirect ) -tool gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit +tool go.maximumdirect.net/releasekit/cmd/releasekit diff --git a/go.sum b/go.sum index 46decf4..f1e7c51 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -gitea.maximumdirect.net/eric/ReleaseKit v0.4.2 h1:P2eDQ29BgyYqHnaz5MV+vzOk7r3s8py2y9k8JISFtJ0= -gitea.maximumdirect.net/eric/ReleaseKit v0.4.2/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI= github.com/yuin/goldmark/v2 v2.1.6 h1:igy0q9JcqovoFXFx/XjtU8sHVhcUHavPj2sA3EoKJUE= github.com/yuin/goldmark/v2 v2.1.6/go.mod h1:G6M4/qOFtfn01/o14BU1UR2Lo5N3S9Qo7xCuz/sHjGQ= +go.maximumdirect.net/releasekit v0.6.0 h1:wbzMeQGeLjXigBSN9j9iJLf/CC3htjzt9iEwubezgy0= +go.maximumdirect.net/releasekit v0.6.0/go.mod h1:QE4BLrAhVJTzi+/YTcVl21K/qZp9ojRLg5kuoy3Zb44= go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/releasekit.json b/releasekit.json index 9dbcb13..9bceb8d 100644 --- a/releasekit.json +++ b/releasekit.json @@ -3,10 +3,18 @@ "remote": "origin", "expected_push_url": "ssh://git@forge.maximumdirect.net:22222/base-layer/configkit.git", "validation": { + "forbidden_files": [".releasekit/gitea-token"], "go": { "race": true, "full_vet": true, "compile_targets": ["linux/amd64", "linux/arm64"] } + }, + "delivery": { + "forge": "gitea", + "base_url": "https://forge.maximumdirect.net", + "owner": "base-layer", + "repository": "configkit", + "token_file": ".releasekit/gitea-token" } } diff --git a/releasekit_profile_test.go b/releasekit_profile_test.go deleted file mode 100644 index ea0c7d5..0000000 --- a/releasekit_profile_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package configkit - -import ( - "bytes" - "encoding/json" - "os" - "reflect" - "testing" -) - -func TestReleaseKitProfile(t *testing.T) { - data, err := os.ReadFile("releasekit.json") - if err != nil { - t.Fatal(err) - } - - var profile struct { - Product string `json:"product"` - Remote string `json:"remote"` - ExpectedPushURL string `json:"expected_push_url"` - Validation struct { - Go struct { - Race bool `json:"race"` - FullVet bool `json:"full_vet"` - CompileTargets []string `json:"compile_targets"` - } `json:"go"` - } `json:"validation"` - } - - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - if err := decoder.Decode(&profile); err != nil { - t.Fatalf("decode release profile: %v", err) - } - - if profile.Product != "ConfigKit" { - t.Errorf("product = %q, want ConfigKit", profile.Product) - } - if profile.Remote != "origin" { - t.Errorf("remote = %q, want origin", profile.Remote) - } - wantPushURL := "ssh://git@forge.maximumdirect.net:22222/base-layer/configkit.git" - if fixturePushURL := os.Getenv("CONFIGKIT_TEST_EXPECTED_PUSH_URL"); fixturePushURL != "" { - wantPushURL = fixturePushURL - } - if profile.ExpectedPushURL != wantPushURL { - t.Errorf("expected push URL = %q, want %q", profile.ExpectedPushURL, wantPushURL) - } - goValidation := profile.Validation.Go - if !goValidation.Race || !goValidation.FullVet { - t.Errorf("race and full vet must be selected: race=%t full_vet=%t", goValidation.Race, goValidation.FullVet) - } - wantTargets := []string{"linux/amd64", "linux/arm64"} - if !reflect.DeepEqual(goValidation.CompileTargets, wantTargets) { - t.Errorf("compile targets = %v, want %v", goValidation.CompileTargets, wantTargets) - } -}