Files
pristontale-eu-linux/RELEASE_PIPELINE_WORKPLAN.md

26 KiB
Raw Blame History

PristonTale EU Linux Launcher Release Pipeline Workplan

This is the tracked implementation plan for manual Gitea releases today and the deferred automatic publication path, stable and prerelease channels, signed manifests, release history, and channel-aware rollback. A checked item means the repository contains the implementation or the audit evidence named beside it. Publishing tests that require a configured Gitea server remain unchecked until they have run against that server.

Legend: [x] complete, [ ] pending.

Current milestone

  • Save the release workplan in the repository.
  • Inspect the existing release, update, rollback, settings, UI, and telemetry paths.
  • Implement and validate the automatic publication pipeline for future use.
  • Switch to temporary manual-publication mode: release.sh now creates a signed, verified dist/v<VERSION>/ package without commits, tags, pushes, API calls, or uploads.
  • Disable automatic workflow push triggers so manual Gitea releases cannot race an Actions publication job.
  • Store normal update payloads directly at the tar root while retaining runtime extraction support for historical archives and the 0.9.10–0.9.11 compatibility window.
  • Re-enable automatic Gitea publication when requested.
  • P1: add stable/prerelease discovery and launcher channel support.
  • P2: harden remote rollback and managed-file reconciliation.
  • P3: complete signing migration and release quality improvements.

Phase 0 — Existing-system audit

  • Repository structure inspected. Runtime sources are in ptlinux/ and ptdiag/; installer assets are in packaging/; the checked-in channel is in updates/; local, ignored development tools and tests are in tools/ and tests/.
  • Authoritative launcher version identified as ptlinux/__init__.py. pyproject.toml is a required synchronized metadata mirror.
  • Current update archive identified as pristontale-eu-linux-update-v<VERSION>.tar.gz.
  • Current archive layout identified. Its root is pristontale-eu-linux-update-v<VERSION>/, containing ptlinux/, a compact ptdiag/ subset, and release.json.
  • Current update manifest identified as signed JSON schema 1 in updates/stable.json, with version, channel, minimum updater version, archive URL/size/SHA-256, publication time, and notes URL.
  • Current update URL identified as the raw main branch updates/stable.json; PTLINUX_UPDATE_MANIFEST_URL can override it.
  • Latest-release discovery identified as the mutable signed stable manifest. The launcher does not use Gitea /releases/latest.
  • Current rollback identified as local retained release directories selected by atomic current and previous symlinks.
  • Cached archives identified in the launcher download cache and reused only when their size and SHA-256 match the authenticated manifest.
  • Current staging identified: download to a .part file, verify while downloading, safely extract to a staging directory, validate/self-import, move into the immutable release store, then atomically replace symlinks.
  • Atomic activation confirmed through same-directory temporary symlink plus os.replace() and directory fsync.
  • Removed launcher files are handled because each release is a complete, separate directory. No in-place overlay is used.
  • Fixed release asset names are not required by the current launcher; the authenticated manifest supplies the archive URL.
  • Existing manually generated files identified: installer tar/zip archives, launcher update tar archive, SHA-256 files, stable manifest/signature, and release uploads.
  • Existing manual exclusions identified. The launcher update builder selects Python/runtime files explicitly and excludes installer-only modules; the AIO builder selects runtime, packaging, installer, and README files explicitly.
  • SHA-256 generation and verification confirmed for archives.
  • Existing asymmetric signature support confirmed. The manifest is signed and verified with the bundled public key through OpenSSL (currently RSA with SHA-256); the private key is ignored under release-keys/.
  • Existing telemetry inspected and extended with channel, target release, manifest identity, and complete update/rollback lifecycle metadata.
  • Decision: extend the existing signed-manifest and atomic release-directory updater instead of introducing a second updater.

Release model and invariants (phases 1–2, 19, 24–25, 32–38, 45–51, 61–63, 77, 84)

  • Support exactly stable and prerelease channels in the initial model.
  • Accept stable tags vMAJOR.MINOR.PATCH and prerelease tags ending in -beta.N or -rc.N; reject channel/tag mismatches.
  • Treat the manually selected Gitea release tag as the authoritative, immutable release identity.
  • Restore annotated-tag and channel-pointer monotonicity enforcement when automatic publication is re-enabled. Manual publishers must currently check the tag in Gitea and the version ordering before copying a channel pointer.
  • Use one semantic-version parser and one channel/update resolver; never use lexical version ordering.
  • Preserve ordering from beta to RC to final, including multi-digit fields.
  • Keep the internal launcher version without the leading v.
  • Build immutable, versioned assets named PristonTale-EU-<VERSION>.tar.gz; normal releases place ptlinux/, ptdiag/, and release.json directly at the archive root.
  • Accept both new flat archives and legacy version-folder archives in the updated launcher so retained releases and rollback remain compatible.
  • Configure stable 0.9.10 and its 0.9.11 UI repair as legacy-wrapped bridge releases so deployed 0.9.8 launchers can install either one automatically.
  • Mark flat manifests as requiring updater 0.9.9 so legacy clients fail closed before download/extraction rather than entering a doomed transaction.
  • Keep final manifests/signatures/checksums outside the archive they describe.
  • Keep signed metadata mandatory for trusted automatic update operations.
  • Stable clients can install stable releases only.
  • Prerelease clients resolve the greatest eligible stable or prerelease semantic version.
  • Changing prerelease to stable never causes an automatic downgrade.
  • Model release status as active, withdrawn, or blocked; do not offer withdrawn/blocked releases as new automatic updates.
  • Use each historical release's own signed manifest and immutable versioned archive; verify the embedded archive checksum and every target file hash.
  • Persist the signed per-file inventory at install time and re-verify it before reactivating a locally retained release; preserve compatibility for releases installed before integrity records existed.
  • Never reuse or overwrite a published tag or its assets.
  • Fail rather than duplicating an existing Gitea release.
  • Preserve compatibility for existing stable installations during migration.

Release tooling and configuration (phases 3–7, 12–14, 18, 20–23, 26–27, 65–66)

  • Add .gitea/workflows/release.yml for the deferred automatic publisher; keep it manual-dispatch only while manual upload mode is active.
  • Add tracked scripts/release.sh, scripts/build-release.sh, scripts/generate-manifest.py, and scripts/verify-release.sh.
  • Add release/config.json with application, release branch, archive naming, manifest schema, channels, selected payload, required files, and exclusions.
  • Keep generated dist/ output ignored.
  • Explicitly exclude Git/IDE metadata, logs, caches, credentials, developer environment files, downloads, test output, and other project-specific files.
  • Implement the primary CLI as ./scripts/release.sh CHANNEL VERSION with aliases normalized to prerelease.
  • Validate the repository root and channel/version pair, then run configured preflight checks before creating local artifacts.
  • Support --dry-run, --skip-tests, and an explicit local signing key.
  • Isolate preflight stdin so nested CLI tests cannot open an interactive release-note prompt, and prefer the repository .venv interpreter when present.
  • Set the requested version only in the staged package so a manual build does not edit the authoritative source version or synchronized project metadata.
  • Leave commit, tag, push, Gitea release creation, and asset upload to the developer while manual publication mode is active.
  • Restore remote, branch, clean-worktree, tag-absence, version-commit, annotated-tag, and ordered-push handling when automatic publication resumes.
  • Make dry-run perform validation, notes preview, and optional tests without commit, tag, push, or release creation.
  • Make build-release.sh VERSION CHANNEL OUTPUT_DIR independently build an explicitly selected staging tree and clean generated output.
  • Normalize archive ordering, timestamps, ownership, group, and permissions where practical.
  • Generate archive SHA-256 and a sorted per-file manifest without absolute paths.
  • Include schema, application, version, channel, tag, commit, archive metadata, release metadata, timestamp, status, compatibility, and file hashes.
  • Verify archive extraction, required entrypoints, forbidden content, checksum, manifest consistency, every managed file hash, and signature before publication.
  • Give each archive download a signed SHA-256 query key so a CDN cannot keep serving a pre-upload empty response from the immutable Gitea asset path.

Manual Gitea publication milestone

  • Produce the versioned archive, archive checksum, signed manifest, and release notes together under dist/v<VERSION>/.
  • Preserve other locally built versions and clean only the selected version directory when rebuilding it.
  • Verify both the normal flat archive and configured legacy-wrapped bridge; the updater installs either payload successfully.
  • Print the exact five paths to upload and the matching v<VERSION> tag.
  • Print whether Gitea's Pre-release checkbox must be selected for the chosen channel.
  • Generate a signed dist/v<VERSION>/channel/<channel>.json{,.sig} pair for the developer to copy to updates/ after the Gitea assets exist.
  • Require all five Gitea attachments to be uploaded with nonzero sizes before publishing the generated channel pointer.
  • Repair the live v0.9.11 stable pointer after Cloudflare cached a pre-upload zero-byte archive response. The pointer now uses the archive's signed SHA-256 cache key, and the deployed 0.9.10 updater completed its full remote verification against the repaired channel.
  • Safely discard generated-only __pycache__ content before validating a signed managed release, while rejecting unexpected cache contents; remove the local diagnostic cache that blocked activation and successfully complete the 0.9.10 to 0.9.11 transaction.
  • Keep the other channel pointer unchanged.
  • Manually regenerate signed updates/history.json{,.sig} from the live manifest-backed Gitea releases so remote history and rollback discovery no longer return HTTP 404; automatic regeneration remains deferred.

Release notes (phases 8–11)

  • Support --description and Markdown-preserving --description-file.
  • Generate commit notes from the previous stable tag for stable releases.
  • For prereleases, prefer the previous prerelease of the same target version; use the previous stable tag for the first prerelease.
  • Put developer text before generated commit notes by default.
  • Support --no-auto-changelog, --changelog-only, and --editor using ${EDITOR:-nano}.
  • Allow interactive Ctrl+D release-note entry only on an interactive stdin; use generated notes in noninteractive environments.
  • Preview the final release notes before building and copy them unchanged to dist/v<VERSION>/release-notes.md for manual use as the Gitea release body.

Deferred Gitea Actions publication (phases 15–17, 28–31, 58–60, 64)

  • Implement the v* tag-push publisher and CI tag validation for future use.
  • Remove its tag-push trigger during manual publication mode; only an intentional manual dispatch can start the retained workflow.
  • Derive tag, version, channel, prerelease flag, and dereferenced commit SHA from the exact checked-out annotated tag.
  • Prefer GITHUB_REF_NAME, GITHUB_REPOSITORY, GITHUB_API_URL, GITHUB_SERVER_URL, GITHUB_OUTPUT, GITHUB_ENV, and GITHUB_STEP_SUMMARY context values.
  • Use actions/checkout@v4 and build the exact tagged commit.
  • Require a minimally scoped RELEASE_TOKEN without logging it.
  • Keep the manifest private signing key only in an Actions secret, write it with mode 0600 when needed, and remove it immediately after signing.
  • Run validation/tests, build, manifest generation, signing, and complete verification before creating a Gitea release.
  • Create draft=false releases through the Gitea API with the correct prerelease flag, target commit, title, and release notes; require a successful response and release ID.
  • Upload archive, checksum, manifest, manifest signature, and release notes separately; fail on every required upload error.
  • Re-query the release and verify attachment names/sizes and the prerelease flag.
  • Print a release summary containing title, channel, tag, commit, archive, SHA-256, manifest, and publication status.
  • Document Gitea Actions, repository Actions, isolated act_runner, required commands, trusted tag-only secret exposure, and minimum permissions in release/README.md.
  • Verify Actions enablement and the registered release runner on the live Gitea.

Channel indexes and history (phases 32–33)

  • Publish independently signed stable.json and prerelease.json channel metadata at stable URLs without cross-channel mutation.
  • Publish signed release history with version, channel, tag, manifest URL, signature URL, date, notes URL, status, and compatibility metadata.
  • Follow the Gitea release-list pagination contract so old rollback releases remain in history after the first 100 API results.
  • List only real Gitea releases that retain their own manifest and signature.
  • Rebuild status-aware channel manifests after a withdrawal/block so legacy channel consumers no longer receive an inactive channel head.

Launcher channels, resolver, and UI (phases 34–44)

  • Add validated update_channel setting with stable default so existing users remain stable.
  • Add Stable/Prerelease settings UI with clear descriptions.
  • Immediately check for updates after changing the selected channel.
  • Do not downgrade automatically after switching prerelease to stable.
  • Refactor discovery, update eligibility, semantic ordering, channel transitions, and inactive-release filtering through one UpdateResolver.
  • Connect signed remote history and remote rollback choices to UpdateResolver.
  • Show current/target versions, target channel, release date/title/summary, notes, and a clear prerelease label in the update UI.
  • Populate version history from actual signed release history and show version, channel, date, state, notes, and install/rollback action.
  • Add All/Stable/Prerelease history filters with channel-appropriate defaults.
  • Stable rollback lists stable releases by default; prerelease rollback lists earlier prereleases and stable releases with labels.
  • Require explicit action and a configuration-compatibility warning for prerelease-to-stable rollback.
  • Label the channel of each locally retained release and warn on an explicit cross-channel local rollback.

Rollback transaction hardening (phases 46–50, 52–53)

  • Reconcile managed files by activating a complete, target-manifest-verified immutable release directory; files absent from the target disappear without deleting any unmanaged path.
  • Keep config, logs, saves, downloads, and user-created/unmanaged files outside managed-file deletion.
  • Add configuration schema compatibility metadata and reject unsafe rollback.
  • Preserve each installed release's signed configuration compatibility in its local integrity record and allow signed release-history overrides for withdrawn or compatibility-reclassified historical releases.
  • Precheck release/manifest/signature/archive, free space, processes, writability, and supported schemas.
  • Snapshot current managed state through immutable release links, then download/verify/stage/reconcile/apply/health check, and restore the snapshot after local transaction failure.
  • Permit automatic local restore after failed update application, while prohibiting telemetry-driven remote downgrade.
  • Optionally add advanced latest/pinned update policy.
  • Retain 30 bounded local update/rollback history records.

Telemetry and hotfix integration (phases 54–57)

  • Include current/target version and channel, selected channel, tag, manifest schema/hash, and rollback flag in update telemetry.
  • Add discover/download/apply success and failure event families.
  • Add requested/download/apply/restore rollback event families with from/to versions/channels and an enumerated reason.
  • Keep full Markdown descriptions out of telemetry; use version/tag/channel and optional release ID only.
  • Make hotfix targeting channel-aware and exact-version aware while reusing the release hash/signature verification path.

Failure and security behavior (phases 27–31, 59–63)

  • Reject .env, private keys, SSH keys, Git/IDE metadata, logs, caches, and bytecode in published archives.
  • Never expose release credentials to pull-request workflows or untrusted code.
  • Stop publication after test, build, manifest, signing, release-create, or required-asset failure; never report a partial release as successful.
  • Keep failed tags immutable and publish a new version after correcting a failure.

Verification matrix (phases 67–76)

  • End-to-end stable release: stable flag, artifacts, signed manifest, stable index update, unchanged prerelease index, and stable discovery.
  • End-to-end beta/RC release: prerelease flag, artifacts, prerelease index update, unchanged stable index, prerelease discovery, and stable exclusion.
  • Exercise inline/file/multiline Markdown, generated notes, no-auto, changelog-only, and editor modes; compare preview with Gitea body.
  • Exercise both channel preferences and resolver transitions, including no implicit downgrade after returning to stable.
  • Verify semantic ordering, including 1.10.0 > 1.9.9, stable filtering, and prerelease-to-final transition.
  • Exercise stable rollback, prerelease rollback, cross-channel rollback, managed-file removal, config/log survival, and failed-apply restoration.
  • Exercise withdrawn-release filtering/warnings and valid successor selection.
  • Simulate Gitea/API 401, 403, 404, 409, 422, 500, lookup/create/upload timeouts, and attachment failure without false success.
  • Seed forbidden path cases and verify exclusion/failure and absence from the deterministic archive.
  • Verify valid signatures and reject modified manifest metadata; require signed metadata in CI.

Acceptance criteria (phases 78–83)

  • ./scripts/release.sh stable 1.8.0 --description-file ... validates, previews, builds, signs, and verifies the five files needed for a manual stable Gitea release without changing Git or contacting Gitea.
  • ./scripts/release.sh prerelease 1.9.0-beta.1 --description ... produces the equivalent prerelease upload set and clearly instructs the developer to select Gitea's Pre-release checkbox.
  • Stable-to-prerelease checks immediately; prerelease-to-stable retains the installed beta until a greater eligible stable exists.
  • Cross-channel rollback fetches and verifies the target release, warns, snapshots, reconciles managed files, applies and health-checks, records telemetry, and preserves user state.
  • Normal development remains ordinary commits/pushes. A manual release begins with ./scripts/release.sh stable VERSION or ./scripts/release.sh prerelease VERSION; archive, checksum, manifest, signature, and release notes generation require no manual work.
  • Re-enable Gitea release creation, asset upload, and signed channel/history publication after the manual release process has been proven in production.

Implementation priority

P0 — Build first

  • Inspect existing updater/release system.
  • Semantic-version handling.
  • Manual release CLI and all description/changelog/preview modes.
  • Stable/prerelease tag validation.
  • Retained Gitea publisher implementation with archive, SHA-256, manifest, signature, release creation, prerelease flag, upload, and verification.
  • Disable its automatic trigger for the current manual publication milestone.

P1 — Channel support

  • update_channel setting and settings UI.
  • Signed channel metadata and channel-aware discovery.
  • Central UpdateResolver and semantic ordering.
  • Signed release history, release notes in update UI, and channel-aware rollback candidates.
  • Keep the inline release-notes preview in a fixed-height scrollable area so the version table, channel filter, and update controls remain visible.

P2 — Rollback hardening

  • Managed-file reconciliation and snapshot/restore validation.
  • Cross-channel rollback and rollback telemetry.
  • Withdrawn state and bounded local update history.

P3 — Security and quality

  • Plan a compatible Ed25519 key migration from the deployed RSA verifier.
  • Improve and measure reproducibility for archive and manifest output.
  • Integrate channel-aware targeted hotfix verification.
  • Optional version pinning and release notifications.

Deployment prerequisites

  • Manual publication needs no Actions runner, RELEASE_TOKEN, or Gitea signing secret; the developer needs permission to create a repository release and upload attachments in the Gitea UI.
  • Add RELEASE_TOKEN with repository release/content permissions to Gitea Actions.
  • Add the manifest signing private key as a protected Gitea Actions secret.
  • Confirm Actions, the repository workflow, and an isolated release runner are enabled.
  • Confirm the runner image provides Git, Bash, curl, jq, Python 3, tar, gzip, sha256sum, rsync, and OpenSSL/signing support.

The configured SSH remote is reachable and origin/main resolves successfully. The current development environment does not expose either Actions secret, and the public Gitea API currently returns empty HTTP 200 responses, so runner, repository-Actions, and secret state cannot be authenticated from this checkout.

Validation evidence

  • 68 release-pipeline, resolver, channel, history, signing, rollback, telemetry, CLI, publisher, status, and API-failure tests pass locally.
  • All 575 current project tests pass during the complete manual release preflight.
  • The published flat stable 0.9.9 package demonstrated the expected compatibility boundary: deployed 0.9.8 clients reject its declared minimum updater instead of attempting an archive layout they cannot extract.
  • A real wrapped stable 0.9.10 bridge under dist/v0.9.10/ built, signed, verified, passed updater staging, and is offered automatically to 0.9.8. Its only tar root is pristontale-eu-linux-update-v0.9.10; archive SHA-256: 76df8942d2f0b3b533eb967f8a2379b2a7bdd44fd58305d0f8bf061778fbd9ea.
  • A real wrapped stable 0.9.11 UI repair under dist/v0.9.11/ built, signed, and verified with minimum updater 0.5.0. Its only tar root is pristontale-eu-linux-update-v0.9.11; archive SHA-256: 60b13d6fbc8fe27714c7fe1a65fa73785a13962d96d03decfac1aa62c4e3f6db.
  • Publish the prepared v0.9.11 release and its five assets in Gitea. The remote currently has no v0.9.11 tag, so its stable channel still resolves to 0.9.10 even when the PyCharm checkout is running 0.9.11.
  • After the Gitea assets exist, commit and push the prepared updates/stable.json and updates/stable.json.sig pair that points stable discovery to 0.9.11.
  • Keep the 0.9.11 embedded notes to one concise change line so clients still running the overflowing dialog can reach the install action.
  • Hidden-window Tk geometry validation at the default scale confirms the notes viewport, channel filter, 124-pixel history table, and footer all fit inside the 820×680 update dialog.
  • Reproduce the reported 0.9.10 layout from its published archive: the old dialog requests 820×828 pixels while forcing a 760×620 window. Confirm the 0.9.11 archive uses the repaired dialog and keeps notes, history, and footer in separate non-overlapping bounds; its deliberately short channel notes also leave the old 0.9.10 install button reachable during the repair upgrade.
  • Synchronize the workspace version source and project metadata to 0.9.11, and make the shared PyCharm launcher configuration put $PROJECT_DIR$ first on PYTHONPATH, so debug runs load the current checkout rather than an older installed release.
  • Keep update-dialog version labels tied to the running launcher. When a PyCharm checkout is newer than the active installed symlink, show the checkout as Current and describe the older on-disk release as Installed.
  • Prior deterministic-build validation produced byte-identical stable 0.9.8 archives with SHA-256 72346f216856235df3c6071409e2bbce17853b9f9589bf3f012743ddf7823f4b.
  • Shell syntax, JSON parsing, Git whitespace checks, PHP telemetry endpoint syntax, Python compilation, and IDE error inspections pass.
  • Run stable and prerelease publication acceptance tests against the live Gitea after the protected secrets and isolated runner are configured.