Skip to content

Release process

Versioning

qkt uses semantic versions while pre-1.0:

  • minor (0.X.0): a substantial phase or intentionally breaking public change
  • patch (0.X.Y): a compatible feature set, sub-phase, or hotfix

VERSION is the single release version consumed by Gradle, the CLI, generated scaffolds, distribution names, and container metadata. Change it through a reviewed PR before creating a tag.

Distribution channels

Channel Source Intended use
GitHub release and ghcr.io/elitekaycy/qkt:vX.Y.Z immutable vX.Y.Z tag canonical stable install
ghcr.io/elitekaycy/qkt:latest main current promoted main
ghcr.io/elitekaycy/qkt:edge testing integration-tested staging
ghcr.io/elitekaycy/qkt:dev main authoring image with editor tools

Generated deployment projects pin v<VERSION>. Do not publish a qkt build whose tag differs from VERSION: both release and Docker workflows run scripts/verify-version-tag.sh, and the essentials workflow tests that gate.

Promotion

Changes reach a release through the protected branch flow:

  1. Merge reviewed feature branches into dev.
  2. Let the green check.yml merge the exact dev commit into testing while preserving prior testing promotion history.
  3. Wait for integration.yml and the :edge image on the exact testing SHA.
  4. Resolve the edge image to its immutable ghcr.io/.../qkt@sha256:... digest and validate it with one of the two evidence profiles below. A normal paper-soak profile runs against the demo MT5 canary for at least 48 continuous hours or five trading days. A finite live-parity profile may qualify the promotion without that elapsed-time requirement, but it must exercise real demo strategy runs across indicators, math, DSL, order types, ticks, bars, fills, and Insights, and retain exact replay comparisons with zero unexplained mismatches or outcomes. Both profiles retain periodic /health, final qkt reconcile --json, and qkt golden capture output with zero dropped records.
  5. On the trusted soak host, derive the attestation:
qkt soak report ema-canary \
  --testing-sha "$testing_sha" --image "$image_digest" \
  --started-at "$started_at" --completed-at "$completed_at" \
  --trading-days "$trading_days" --health health.jsonl \
  --reconciliation reconciliation.json --golden golden.zip \
  --coverage coverage.json --parity parity.json --insights insights.json \
  --out /var/lib/qkt/soak/attestation.json

For live-parity, the attestation additionally sets attestationType to live-parity, includes a parity object with positive counts for strategiesTested, indicatorsTested, mathScenariosTested, dslScenariosTested, orderTypesTested, totalTicks, totalBars, fills, parityComparisons, and insightsEvents, and zero values for parityMismatches, unexplainedRejections, and unexplainedOrderOutcomes. Its artifact set is six sibling files: health, journal, reconciliation, coverage, parity, and insights; every file is hashed in artifactSha256. The verifier requires at least ten minutes of recorded run time and verifies every hash before promotion.

  1. Dispatch paper-soak.yml on the testing ref. It runs on the trusted qkt-paper-soak self-hosted runner, validates hashes and image revision, and publishes immutable promotion evidence:
gh workflow run paper-soak.yml --ref testing \
  -f attestation_path=/var/lib/qkt/soak/attestation.json
  1. Run promote-to-main.yml. It requires successful integration and paper-soak runs for the exact current testing SHA, revalidates the downloaded evidence, and creates or reuses a testing -> main PR naming the soaked digest.
  2. Merge the promotion PR after its required checks.
  3. Wait for integration.yml and docker.yml on the promoted main SHA.

Do not push directly to dev, testing, or main.

Research and deployment consumers must pin the digest recorded in the promotion PR. The moving :edge tag is a discovery channel, not promotion evidence.

Tag And Publish

Tag the exact promoted main commit only after the main checks pass:

git fetch origin
release_sha="$(git rev-parse origin/main)"
version="$(tr -d '\r\n' < VERSION)"
scripts/verify-version-tag.sh "v$version"
git tag -a "v$version" "$release_sha" -m "release: v$version"
git push origin "v$version"

The tag push starts two pipelines:

  • release.yml builds and verifies the ordinary tarball, self-contained Linux bundle, self-contained Windows zip, and VS Code extension, then creates the GitHub release with generated notes.
  • docker.yml smoke-tests the runtime image and publishes the versioned GHCR image with provenance and an SBOM.

The GitHub release event then starts the Winget submission workflow. Do not run gh release create or upload Gradle artifacts manually during the normal path; that duplicates and can race the automated publisher.

Verification

Before announcing the release, verify all of the following:

gh release view "v$version"
gh run list --workflow release.yml --limit 1
gh run list --workflow docker.yml --limit 1
docker pull "ghcr.io/elitekaycy/qkt:v$version"
docker run --rm --entrypoint qkt "ghcr.io/elitekaycy/qkt:v$version" --version

The release must contain the Linux tarballs and Windows zip, and qkt --version from the image must report the tagged version and release SHA.

Failure And Hotfix Policy

Tags are annotated and immutable. Never force-update or delete a published tag. If the tag is wrong, fix forward with the next patch version and explain the superseded release in its notes.

For a hotfix, branch from dev, merge through dev -> testing -> main, increment VERSION, and repeat the same tag workflow. There are no backport branches before 1.0; users update to the latest patch.