Add hosted Gitea release delivery

This commit is contained in:
2026-11-02 14:10:37 +00:00
parent fd73cd0662
commit 367af5feca
6 changed files with 126 additions and 17 deletions

2
.gitignore vendored
View File

@@ -22,7 +22,7 @@
go.work go.work
go.work.sum go.work.sum
# Local ReleaseKit executable # Local ReleaseKit executable and delivery credentials
.releasekit/ .releasekit/
# env file # env file

View File

@@ -2,7 +2,7 @@
**Base Policy Version:** 1.8 **Base Policy Version:** 1.8
**Last Updated**: 2026-09-27 **Last Updated**: 2026-11-02
**Upstream Source:** [oss.md](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/oss.md) **Upstream Source:** [oss.md](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/oss.md)
@@ -221,7 +221,8 @@ Promptkit retains its LGPL-3.0-or-later library grant in
[COPYRIGHT](../../COPYRIGHT). Adapted policy documentation has the separate [COPYRIGHT](../../COPYRIGHT). Adapted policy documentation has the separate
scope identified there and in [attribution](../oss/attribution.md). scope identified there and in [attribution](../oss/attribution.md).
Promptkit publishes source-only Go module tags on Gitea, with no binaries or Promptkit publishes source-only Go module tags and Gitea release pages, with no
separate hosted download page. The [release procedure](../release.md) owns binaries. Each new hosted page reuses the complete checked-in release note,
publication and concrete footer URLs. The hosted-page guidance applies if such including its licensing footer. The [release procedure](../release.md) owns
a channel is deliberately introduced; it does not require creating one. source publication, hosted delivery, and concrete footer URLs. Previously
published notes and tags retain their original records.

View File

@@ -2,7 +2,7 @@
**Base Policy Version:** 1.6 **Base Policy Version:** 1.6
**Last Updated**: 2026-10-08 **Last Updated**: 2026-11-02
**Upstream Source:** [release.md](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/docs/policy/release.md) **Upstream Source:** [release.md](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/docs/policy/release.md)
@@ -241,9 +241,13 @@ navigation on 2026-09-26. Apply them prospectively; previously published notes
and tags retain their original records. and tags retain their original records.
Promptkit publishes source-only Go modules from main to origin on Gitea, using Promptkit publishes source-only Go modules from main to origin on Gitea, using
annotated stable SemVer tags. It has no binaries, hosted download pages, or annotated stable SemVer tags. The source commit is pushed through main before
asynchronous deliverables. The source commit is pushed through main before the the tag. After source publication, maintainers use ReleaseKit locally to deliver
tag. The [release procedure](../release.md) owns commands and recovery. a Gitea release page containing the complete checked-in note, including its
licensing footer. There are no binaries or other deliverables. Source publication
and hosted delivery have separate outcomes; delivery credentials and service
availability do not gate source-release completion. The
[release procedure](../release.md) owns commands and recovery.
Release preparation uses the latest Go installation available in the development Release preparation uses the latest Go installation available in the development
environment to build the maintainer release executable and validate the environment to build the maintainer release executable and validate the
@@ -268,8 +272,10 @@ ReleaseKit tool selected in `go.mod`. The committed
[profile](../../releasekit.json) selects the race-enabled Go suite, full vet, [profile](../../releasekit.json) selects the race-enabled Go suite, full vet,
committed Go formatting, local Markdown links, workspace and filesystem committed Go formatting, local Markdown links, workspace and filesystem
replacement inspection, and the offline semantic checks for the three replacement inspection, and the offline semantic checks for the three
maintained consumer examples. These are the selected checks for the final maintained consumer examples. It also rejects the configured local delivery
candidate; a separate ordinary test run or standalone build is not an token path if committed; this narrowly scoped check is not a general credential
scan. These are the selected checks for the final candidate; a separate
ordinary test run or standalone build is not an
additional release gate. The upstream tool owns candidate, remote, and tag additional release gate. The upstream tool owns candidate, remote, and tag
publication checks. It returns native status 0 for confirmed publication, 1 publication checks. It returns native status 0 for confirmed publication, 1
for a known validation or publication failure, 2 for invalid input or profile, for a known validation or publication failure, 2 for invalid input or profile,
@@ -278,3 +284,9 @@ captured publication endpoint before retrying; the repository-specific
[procedure](../release.md#failure-recovery) links to the upstream recovery [procedure](../release.md#failure-recovery) links to the upstream recovery
instructions. Do not wait for CI or Go module availability. Licensing footer instructions. Do not wait for CI or Go module availability. Licensing footer
URLs are preparation work, not a network gate. URLs are preparation work, not a network gate.
Hosted delivery is a separate post-publication step for new releases. It reuses
the immutable tag and note without rerunning candidate validation or waiting
for CI. Retry failed delivery against that same version after resolving any
uncertain hosted write; preserve published tags and hosted content. Report
delivery status separately from the completed source release.

View File

@@ -4,6 +4,8 @@ This runbook implements the [release policy](policy/release.md). Promptkit
publishes source-only Go module tags from `main` to `origin`. A successful push publishes source-only Go module tags from `main` to `origin`. A successful push
of the selected annotated tag, after its source commit is available on `main`, of the selected annotated tag, after its source commit is available on `main`,
completes the release. Do not wait for CI or Go module availability. completes the release. Do not wait for CI or Go module availability.
After source publication, deliver the hosted Gitea release page as described
below. Report source publication and hosted delivery separately.
## Prepare The Candidate ## Prepare The Candidate
@@ -51,13 +53,22 @@ impact under the [versioning policy](policy/release.md#versioning-and-compatibil
End each new note with the licensing footer below, using the selected version: End each new note with the licensing footer below, using the selected version:
```markdown ```markdown
[COPYRIGHT](https://gitea.maximumdirect.net/eric/promptkit/src/tag/vX.Y.Z/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/promptkit/src/tag/vX.Y.Z/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/promptkit/src/tag/vX.Y.Z/docs/oss/attribution.md) [COPYRIGHT](https://forge.maximumdirect.net/base-layer/promptkit/src/tag/vX.Y.Z/COPYRIGHT) | [LICENSE](https://forge.maximumdirect.net/base-layer/promptkit/src/tag/vX.Y.Z/LICENSE) | [ATTRIBUTION](https://forge.maximumdirect.net/base-layer/promptkit/src/tag/vX.Y.Z/docs/oss/attribution.md)
``` ```
Check the footer paths against the candidate; the future tag need not resolve Check the footer paths against the candidate; the future tag need not resolve
remotely during preparation. Follow the [OSS policy](policy/oss.md) for these remotely during preparation. Follow the [OSS policy](policy/oss.md) for these
links. Promptkit publishes no binaries, hosted download page, or other links. Hosted delivery reuses this complete note without a separate summary.
asynchronous deliverables. Promptkit publishes no binaries, attachments, or checksum manifests.
For hosted delivery, supply a Gitea API token with access to publish releases
through `RELEASEKIT_GITEA_TOKEN` or the ignored local file
`.releasekit/gitea-token`, with owner-only permissions for the file. A nonblank
environment value takes precedence. ReleaseKit trims surrounding whitespace
from file contents. Keep the token out of commits and shared logs. The profile
rejects that exact token path if
committed; it does not scan other files or Git history. Source publication uses
Git authentication and neither reads nor requires the delivery token.
Retire completed roadmaps while preserving deferred work and repairing incoming Retire completed roadmaps while preserving deferred work and repairing incoming
links. Review the intended changes through the ordinary development workflow, links. Review the intended changes through the ordinary development workflow,
@@ -87,7 +98,8 @@ The familiar wrapper delegates to the native ReleaseKit executable selected in
`go.mod`. The committed [profile](../releasekit.json) selects the race-enabled `go.mod`. The committed [profile](../releasekit.json) selects the race-enabled
Go suite, full vet, committed Go formatting, local Markdown links, workspace Go suite, full vet, committed Go formatting, local Markdown links, workspace
and filesystem replacement inspection, and semantic checks for the three and filesystem replacement inspection, and semantic checks for the three
maintained offline consumer examples. ReleaseKit also verifies the committed maintained offline consumer examples. It also checks that the configured local
token path is not committed. ReleaseKit verifies the committed
candidate and publication state, then publishes only the selected tag. Its candidate and publication state, then publishes only the selected tag. Its
checks run once for this candidate; do not repeat them as a separate sign-off. checks run once for this candidate; do not repeat them as a separate sign-off.
@@ -97,8 +109,53 @@ the push outcome remains unknown. Successful publication needs no remote
read-back, CI wait, or installation check. Provider probes and inference calls read-back, CI wait, or installation check. Provider probes and inference calls
are not part of validation. are not part of validation.
## Deliver The Hosted Release
After source publication succeeds, keep a clean checkout at the published tag's
commit and run:
```sh
./scripts/releasekit.sh deliver vMAJOR.MINOR.PATCH
```
The profile selects Gitea at `https://forge.maximumdirect.net`, owner
`base-layer`, repository `promptkit`. With no build targets, ReleaseKit creates
or accepts a stable, non-draft release page containing the complete committed
note and licensing footer. There is no binary build, attachment upload, or
candidate revalidation. Delivery does not publish another tag or wait for CI.
ReleaseKit performs its authenticated, read-only repository preflight during
delivery; no separate preflight is required before source publication. Missing
credentials or an unavailable hosted service leave the source release complete.
Delivery returns status 0 for completion, 1 for an operational failure,
conflict, or partial delivery, 2 for invalid configuration or credential/access
errors, and 3 for an unresolved hosted write. Report the hosted URL on success
and retain the diagnostic and delivery state on failure. See the upstream
[delivery guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.7.0/docs/consumers/delivery.md)
for the full result contract.
For a later retry, use a clean checkout of the original tag with its recorded
module files and make the token available locally. For example, in a separate
checkout with the tag available:
```sh
git switch --detach vMAJOR.MINOR.PATCH
./scripts/releasekit.sh deliver vMAJOR.MINOR.PATCH
```
The launcher builds the tool selected by that checkout. Matching hosted content
is accepted; conflicting content is not overwritten. Resolve an unknown hosted
write before retrying. Retry delivery with the same immutable version rather
than rerunning `publish` or moving a tag. Corrections to published source or
content require a new appropriate version. These steps apply to tags containing
this delivery configuration; older releases retain their original workflows.
## Failure Recovery ## Failure Recovery
This section covers source publication. Hosted-delivery recovery is described
[above](#deliver-the-hosted-release); its failure does not undo source publication.
For status 1, use the diagnostic to distinguish a validation failure from a For status 1, use the diagnostic to distinguish a validation failure from a
known publication conflict. Correct and commit candidate defects, push `main`, known publication conflict. Correct and commit candidate defects, push `main`,
and rerun publication with an unused version when appropriate. Status 2 means and rerun publication with an unused version when appropriate. Status 2 means

View File

@@ -2,6 +2,7 @@
"product": "Promptkit", "product": "Promptkit",
"remote": "origin", "remote": "origin",
"validation": { "validation": {
"forbidden_files": [".releasekit/gitea-token"],
"go": { "go": {
"race": true, "race": true,
"full_vet": true "full_vet": true
@@ -13,6 +14,13 @@
"local_replacements": true "local_replacements": true
} }
}, },
"delivery": {
"forge": "gitea",
"base_url": "https://forge.maximumdirect.net",
"owner": "base-layer",
"repository": "promptkit",
"token_file": ".releasekit/gitea-token"
},
"checks": [ "checks": [
{ {
"args": ["python3", "./scripts/check-examples.py"], "args": ["python3", "./scripts/check-examples.py"],

View File

@@ -216,6 +216,10 @@ def prepare_repository(repository, remote, environment, module_bytes, version):
def verify_publication(repository, remote, temporary_root, environment, module_bytes, selected_go): def verify_publication(repository, remote, temporary_root, environment, module_bytes, selected_go):
profile = json.loads((repository / "releasekit.json").read_text(encoding="utf-8"))
token_path = repository / profile["delivery"]["token_file"]
if token_path.exists() or environment.get("RELEASEKIT_GITEA_TOKEN"):
raise RuntimeError("publication fixture must not have delivery credentials")
selected_releasekit = run( selected_releasekit = run(
["go", "list", "-mod=readonly", "-m", "-f", "{{.Version}}", RELEASEKIT_MODULE], ["go", "list", "-mod=readonly", "-m", "-f", "{{.Version}}", RELEASEKIT_MODULE],
cwd=repository, cwd=repository,
@@ -387,6 +391,31 @@ def verify_validation_failure(
raise RuntimeError(f"failure fixture checkout is not clean:\n{status}") raise RuntimeError(f"failure fixture checkout is not clean:\n{status}")
def verify_forbidden_token(repository, environment):
profile = json.loads((repository / "releasekit.json").read_text(encoding="utf-8"))
token_path = profile["delivery"]["token_file"]
write_file(repository / token_path, "disposable fixture placeholder\n")
note_path = f"docs/releases/{FAILED_RELEASE_VERSION}.md"
write_file(repository / note_path, f"# Promptkit {FAILED_RELEASE_VERSION}\n\nFixture.\n")
repo_git(repository, environment, "add", "--", note_path)
repo_git(repository, environment, "add", "-f", "--", token_path)
repo_git(repository, environment, "commit", "-m", "Add forbidden-path fixture")
result = execute(
[str(repository / ".releasekit/bin/releasekit"), "check", FAILED_RELEASE_VERSION],
cwd=repository,
environment=environment,
)
if (
result.returncode != 1
or "forbidden-files" not in result.stderr
or token_path not in result.stderr
):
raise RuntimeError(
f"committed delivery token path was not rejected:\n{result.stdout}\n{result.stderr}"
)
def verify_usage_classification(binary, temporary_root, environment): def verify_usage_classification(binary, temporary_root, environment):
result = execute( result = execute(
[str(binary), "unknown"], cwd=temporary_root, environment=environment [str(binary), "unknown"], cwd=temporary_root, environment=environment
@@ -404,6 +433,7 @@ def verify_usage_classification(binary, temporary_root, environment):
def main(): def main():
environment = os.environ.copy() environment = os.environ.copy()
environment.pop("RELEASEKIT_GITEA_TOKEN", None)
environment.update( environment.update(
{ {
"GOTOOLCHAIN": "local", "GOTOOLCHAIN": "local",
@@ -464,11 +494,12 @@ def main():
module_bytes, module_bytes,
selected_go, selected_go,
) )
verify_forbidden_token(repository, environment)
except (RuntimeError, OSError, json.JSONDecodeError) as error: except (RuntimeError, OSError, json.JSONDecodeError) as error:
print(error, file=sys.stderr) print(error, file=sys.stderr)
return 1 return 1
print("ReleaseKit publication success and failure fixtures passed") print("ReleaseKit publication and committed-token-path fixtures passed")
return 0 return 0