Skip to content

Release Publication Stages

github-actions[bot] edited this page Oct 18, 2026 · 1 revision

Release Publication Stages

Audience: release maintainers and qualification owners.

Thread context: stable-v1 publication and the repository readiness contract.

Platform/backend scope: Windows 11 x64, MSVC v143, D3D11 and NullRHI.

Candidate qualification

Publication uses python3 tools/site-data/validate.py --require-candidate-ready. The final --require-ready check still requires all work and gates complete; it is not the entry condition for publishing the evidence needed to finish.

The profile's publicationFinalization object declares the terminal work IDs, their required terminal gate, and the stable-release environment. Those IDs must exactly match applicable work explicitly typed with completionPhase: publication-finalization. This is a closed metadata schema, not an arbitrary exemption list. Technical work cannot depend on a finalizer.

Before publication, all other blocking items and transitive dependencies must be done, with no planned verification left. All required technical gates must pass. Gates that also await a finalizer must be at-risk, with their other work complete; they cannot be described as passing. Finalizers must stay in-progress. REL-190 owns supported-host rehearsal, migration/rollback/recovery drills, qualification sign-off, the profile-required-gates and release-approval jobs, and their planned rehearsal selectors. It must be done before candidate publication. REL-200 depends on it and owns only publish/download/site finalization, with no deferred technical job or rehearsal selector. A finalizer carrying planned verification is rejected. Every profile and global state stays candidate, with an assigned owner and reviewed qualification sign-off. Nothing in this procedure promotes the current blocked ledger or substitutes fixture tests for real qualification evidence.

Contract reference rules

validate.py also enforces these rules on every run. A capability can be ready only if it names at least one required gate and has evidence. A gate can be passing only if it has evidence. Every work-item ID (such as RDY-000) and gate ID (such as G00) named in contract text must be declared, including rationale, summaries, limitations and website copy. Every entryPoints, documentationUpdates and sourceContext path of a work item, open or done, and every docs-catalog path must exist on disk. A planned output is referenced only after it lands; tests and CI jobs that do not exist yet are declared in plannedTestSelectors and plannedCiJobs instead.

Work-item commands are resolved against CMakePresets.json, using the inheritance resolver in Tools/buildmatrix/inventory.py. cmake --preset <name> must name a configure preset, cmake --build --preset <name> a build preset, and ctest --preset <name> a test preset. Every build/<dir> tree passed to cmake or ctest must be a configure preset's binaryDir. A ctest run must target a preset that builds tests. A preset whose resolved BUILD_TESTS is false, such as windows-shipping or linux-shipping, is accepted only when the same item configures that preset with -DBUILD_TESTS=ON (as RDY-010 and PLT-200 do). Otherwise the run belongs on a validation preset such as windows-release (-C Release), linux-gcc-release, or ci-linux-asan. A preset an item will add is declared in PLANNED_CMAKE_PRESETS in validate.py and keyed to its owning item. Only that owner's cmake commands may name it, and never a ctest tree. The entry is an error once the preset exists, the owner is done, or the owner stops naming it.

Hand-written counts are governed too. validate.py uses the publisher's document discovery to scan the catalog's root documents and recursive Markdown roots. The same registry always includes Markdown under wiki/, docs/readiness/, and docs/status/, even when the publication catalog omits them. It checks a number followed (within two words) by tests, files, panels, modules, subsystems, backends, lines, nodes, capabilities, features, components, systems, shaders, commands, pages, gates, work items or criteria (including singular forms). N+, ~N, N/M and phrases wrapped across lines are checked; product versions and patch identifiers are not inventory totals. <!-- AUTO:* --> blocks, fenced code, blockquotes explicitly labelled Acceptance criteria: or Ledger note:, generated reference pages under wiki/reference/ and docs/api/, the fully generated wiki/advanced/Codebase-Statistics.md, and the sed_replace patterns of docs/update-readme-badges.sh are generator-owned and skipped. Each pattern is skipped only on the file(s) its own sed_replace call rewrites (the validator resolves $readme, loop variables, and arrays; an unresolvable target fails validation), so N specialized panels is managed on README.md but must be claimed on wiki/getting-started/FAQ.md. Every other hit must lie wholly inside the text of a readiness.publicNumericClaims entry for that surface; an entry N nodes does not cover ~N nodes, 1/N nodes or #N nodes:

classification Extra field Check
metric metricId Exactly one claim, compared with the value generate.py measures from source; N+ passes while the metric is at least N, and ~N or N/M is refused
static-fact evidencePath The cited path must exist (design constants such as the 4096-node mod JSON budget)
historical none A dated audit or changelog record that is not re-measured

An entry whose text no longer occurs, or holds no claim, is an error, so rewording a page retires its entry in the same change. Prefer removing per-file line counts over registering them: they drift with every edit.

Generated publication is checked against its producer: the readiness handoff must equal render_handoff.py output, and generated API pages must pass the API manifest integrity check. The API output declared by the docs manifest must match the site-data producer's destination. Publication regenerates the API corpus and verifies its source metadata before bundling it. These checks govern generated counts and quoted acceptance criteria; they do not authorize new prose claims.

The architecture flowchart's panel-header inventory is bound to editor.panelHeaders. The separate editor.panels metric counts factory registrations; they describe different inventories. Profile-scope and forbidden readiness wording use the same discovery and prose exemptions, without requiring every page to discuss a release profile. Ordinary blockquotes remain checked. These vocabulary-based checks do not resolve arbitrary status labels to capability entries; RDY-000 tracks that remaining gap. The claim audit records the expanded scan's findings, classifications, and prose corrections.

CTest runs the whole contract suite as site-data-contract. The faster readiness-cross-references runs the strict live validation plus the dependency, promotion, selector, future-path, prose-reference and handoff cases. Both carry the readiness and site-data labels and are registered on non-Windows hosts only; the Linux site-data workflow is the suite's CI home.

Repository site publication checks

The publication entry point, tools/site-data/generate.py, invokes the same Python API producer as docs/generate-api-docs.sh before checking documentation. This materializes the ignored docs/api corpus on a clean checkout without requiring a shell for API generation. It then validates the exact-source API manifest, documentation routes, links and anchors before replacing an existing publication. Broken links and route collisions fail publication; missing API pages are never excused by the link validator.

site-data-publisher-docs registers regression checks for that ordering and failure boundary. Local generation with --allow-dirty --skip-doc-health produces a blocked development bundle. It is not publication evidence and cannot establish live-site adoption or an exact-commit CI result.

Site-data runtime consumer

The consumer lives in tools/site-data/runtime. It has no README of its own: a Markdown file under the lowercase tools/ tree collides with the published Tools/ documentation root on case-insensitive checkouts.

siteDataRuntime.mjs is the repository runtime's display boundary for the generated site-data publication. Construct it with the createFetchLoader export from verifyBundle.mjs. Display the verified commit from the runtime; an optional expected-SHA pin is for fixed snapshots, not the moving site:

import { createFetchLoader } from './verifyBundle.mjs';
import { mountSiteDataRuntime } from './browserRuntime.mjs';
import { SiteDataRuntime } from './siteDataRuntime.mjs';

const runtime = new SiteDataRuntime({
    load: createFetchLoader(new URL('/site-data/', document.baseURI)),
});
const mounted = mountSiteDataRuntime({ runtime });

The existing page supplies [data-site-banner], [data-site-commit], [data-site-freshness], [data-site-content], [data-site-readiness], [data-site-docs], [data-site-search], and optional [data-site-claim="metrics.code.totalLines"] targets. unmount() stops polling and removes the subscription.

index.html calls bootstrap.mjs to mount these targets and start polling. The host supplies the publication directory and its /docs/<slug> router. This repository entry point does not deploy or alter the owner-managed site.

The runtime verifies latest.json, the bundle, exact-CI evidence, and the pointed files before installing a publication. A failed or rejected refresh retains the last verified publication, while snapshot().state and snapshot().banner identify syncing, blocked, stale, or unavailable. The fallback is therefore never presented as current. stopPolling() releases the timer when the page is disposed.

The consumer also checks document/search membership, safe slugs, and exact-commit source URLs before installing a snapshot. Markdown page links and anchors are validated by the publisher. Generated-publication tests exercise the full producer output; in-memory unit fixtures separately test lifecycle and rejection.

Site-data status and trust

site-data-publish.yml now writes status.json beside latest.json for every completed Build SparkEngine run on the current Working commit. A non-success run writes blocked status while leaving latest.json and the last good snapshot byte-for-byte intact. The reference runtime shows the status commit separately from the content commit; retained content never acquires the failed commit's SHA. A successful run still needs the existing exact-gate proof and bundle validation before its content replaces latest.json. The publisher checks the newest run and attempt for that SHA before preparation and again before the ref moves, so a later dispatch cannot republish an older successful attempt over a failed status.

The static runtime uses no credential. Before displaying current or blocked, it queries GitHub's public API for the Working head and the complete bounded Build run list filtered by source SHA. It compares repository, workflow, branch, run ID, latest run number, attempt, status, conclusion, and head SHA with status.json. A current bundle's Build evidence is checked the same way. A missing API response, rate limit, moved head, newer run, rerun, truncated list, or disagreement makes the view stale or unavailable. Hashes still protect the retained bundle's bytes; the browser does not independently attest every subordinate CI-120 or CodeQL field in its exact-evidence manifest. Those fields remain subject to the publisher's exact-gate checks and must not be presented as independently verified by the browser.

SITE_DATA_PUBLIC_REF selects refs/tags/site-data (the default) or refs/heads/site-data. The owner must choose the ref actually served by the public site's /site-data/ path. That publication-channel and live-site adoption decision remains open; repository tests do not prove deployment.

The verifier and consumer tests use the same generated publication selected by SPARK_SITE_DATA_DIR. Run them with:

$env:SPARK_SITE_DATA_DIR = '.site-data'; npm test --prefix tools/site-data/runtime

Protected publication authority

The repository owner must create the stable-release GitHub environment before dispatching a versioned build. This repository has one authorized human release provider, Krilliac, so the environment uses an explicit owner-only contract: configure exactly one required reviewer, the repository owner, and allow that owner to approve a release they initiated. The required environment approval is still mandatory; this is not an approval bypass and no second reviewer is invented. The read-only verifier proves the reviewer identity through the repository API. Use a custom deployment policy with exactly one branch entry named Working. Do not allow wildcard or tag entries. The controller runs from the current trusted Working commit, even when its publication target is a version tag. Keep the existing Working ruleset enforced. Disable administrative protection bypass in the environment's settings. The API must prove can_admins_bypass=false; true, missing, or unrecognized values fail.

The workflow checks these API-visible protections before building, on entry to the publication job, and immediately before publishing. The publisher job itself is bound to stable-release, so GitHub applies the required owner approval gate. Administrative bypass remains disabled. Nightly uses the separate nightly-release environment and cannot qualify stable-v1.

verify_release_environment.py only reads GitHub metadata. Its token needs repository Actions read access for the environment and deployment-branch APIs. The owner must resolve a missing environment, API denial, or unavailable protection feature; none is treated as approval. The preflight never creates an environment, broadens token permissions, or supplies a substitute approval.

Immediately after that entry check, the publisher records the actual approval event with record_release_approval.py. It reads GET /repos/{repo}/actions/runs/{run_id}/approvals (bounded, Link-paginated, duplicate-key-rejecting) and binds it to the run attempt, head SHA, release workflow path, repository, and stable-release environment id. It fails unless the history holds at least one approval, every review is approved by the repository owner Krilliac (login and id) for stable-release only, and no review is rejected. Each environment-bound job needs its own review, so a stable run normally carries more than one owner approval; all of them are recorded. The closed, deterministic record keeps the run id, attempt and start time, the environment id, the approver, and a SHA-256 of each review comment. It is retained as the 90-day artifact stable-release-approval-<SHA>-<run>-<attempt> and its digest becomes the job output approval_record_sha256. The approvals API exposes no review timestamp, so approval time is bounded by the run start and that artifact's upload. An owner-only approval is one person's review, not independent second-person review; how GitHub reports that self-approval still needs a hosted stable run to confirm.

The separate repository /immutable-releases API requires Administration read permission, which GITHUB_TOKEN cannot provide. The owner must provision RELEASE_POLICY_READ_TOKEN in both the stable-release and nightly-release environments, using a GitHub App installation token or fine-grained token scoped to this repository with Administration read (plus implicit Metadata read). It needs no contents write or other write permission. Keep token issuance and renewal outside this workflow's code changes; do not place it in source or a repository variable. Policy proof runs after the environment-bound job starts, not in the unprivileged preparation job. Missing, expired, or denied policy authority fails closed and requires owner setup. Bindings are restricted to the individual policy, guarded-mutation, staging, acceptance, and recovery steps; there is no workflow/job-level binding. Checkout, download, upload, other third-party actions, and unrelated repository steps do not receive this secret.

The policy reader supplies that secret only to its read subprocess. Mutation commands keep the separate GH_TOKEN and do not receive the policy secret. The policy credential is never passed on a command line, printed, or retained in the evidence receipt; guard errors do not render mutation argv or Git auth headers. The independent publication consumer receives neither environment secret.

Provision native Authenticode signing and the publisher thumbprint separately. After the exact stable assets are frozen, the protected stable-release job imports the same PFX with an ephemeral key and creates deterministic detached signatures without exporting the private key. The derived SPKI SHA-256 fingerprint must match the protected SPARKENGINE_STABLE_SIGNATURE_KEY_FINGERPRINT repository variable. The job uploads the resulting flat SparkEngine-release-signature-bundle.tar.gz as an immutable release control asset only after the durable download-counter preflight; the control asset is not a distributable or badge-ledger entry. All existing signature, checksum, SBOM, scan, exact-CI, source/tag, and package qualification gates still apply.

Release tag contract

The prepare job's Compute release metadata step derives every channel tag from two tested helpers; it holds no inline tag logic.

  • .github/scripts/stable_release_tag.py default-version prints the single set(SPARK_ENGINE_VERSION "X.Y.Z" CACHE ...) default and fails when CMakeLists.txt declares SPARK_ENGINE_VERSION zero or several times.
  • .github/scripts/stable_release_tag.py verify vX.Y.Z accepts a dispatched stable tag only when it equals that default and CHANGELOG.md has exactly one ## [X.Y.Z] (optionally ## [X.Y.Z] - YYYY-MM-DD) heading.
  • .github/scripts/nightly_release_tag.py RUN_ID ATTEMPT SHA returns the immutable nightly-<run>-<attempt>-<sha12> tag and rejects a missing or malformed run identity. Nightly has no versioned-changelog requirement.

Every rejection exits non-zero before any metadata output is written. The contract suites run as the CTest selectors TagReleaseContract_StableTag and TagReleaseContract_NightlyTag and in the validate-ci-tools job. test-workflow-failure-propagation.py executes the workflow step itself against fixture trees.

Stable release notes

The stable release body is generated, not hand-written. After the signature bundle is created and before the draft is staged, the Render fail-closed stable release notes step runs tools/release_notes.py and passes its output to the stable staging step as RELEASE_BODY. The notes contain:

  • the stable-v1 support matrix, supported hosts, experimental and unsupported capabilities, excluded gates, and limitations, copied from docs/site/readiness.json at the release commit (the profile state is printed as recorded, never upgraded);
  • the single ## [X.Y.Z] section of CHANGELOG.md, with a ### Migration (or Migrations, Migration notes, Upgrading, Upgrade notes) subsection surfaced as the Migrations section; without one the notes say that no migration subsection is recorded;
  • the asset list, the SHA256SUMS lines, and fixed sha256sum -c, openssl pkey/openssl dgst -verify (with the pinned signer fingerprint), gh attestation verify, and gh release verify instructions.

The generator exits non-zero, and staging never starts, when the version section is missing, duplicated, or empty; SHA256SUMS is empty, malformed, lists an unexpected asset, or misses a distributable; the SBOM or SHA256SUMS is absent from the expected asset list; or the signature control asset is missing, is not a flat gzip tar with a manifest, public key, and signatures, or carries a key whose SPKI SHA-256 differs from SPARKENGINE_STABLE_SIGNATURE_KEY_FINGERPRINT. The output is deterministic and bounded to 120000 characters, so a resumed draft is patched with identical text. Nightly releases keep their short workflow body. ReleaseProfileRehearsal_ReleaseNotes (Tests/Tools/test_release_notes.py) covers the renderer and its wiring; it is not the full REL-190 rehearsal.

Channel retention and support policy

Owner decision OD-17 (owner decisions, 2026-09-24) sets the retention and support terms each channel must meet. REL-100 owns enforcing them. They are policy targets; they do not mean any release has been published under them.

Channel Retention Support
Stable Release assets are immutable and kept permanently Security and critical fixes until 6 months after the next stable release
Nightly Each uniquely tagged immutable nightly is kept for 30 days Unsupported
Experimental Artifacts are kept for 14 days Unsupported; always labeled experimental
CI build artifacts Existing 7- and 90-day Actions retention Not a release channel

The stable window lines up with OD-03 (saves and scenes read N-1): a user on the previous stable release can still load current data during its fix window. No stable-v1 release exists yet, so no stable support window is running.

Where each term is stated or enforced today:

  • Stable and nightly terms are stated in the generated stable notes (SUPPORT_WINDOW in tools/release_notes.py), and the nightly release body states that nightlies are unsupported.
  • The 14-day experimental retention is pinned as retention-days: 14 on the experimental build.yml uploads (ReleaseProfileRehearsal_ReleaseNotes).
  • Nothing enforces the 30-day nightly window yet, and no release text claims it. Deleting an expired nightly release also deletes its assets, and download_counter_ledger.py refuses any tracked asset that disappears without a durable pending publication. That would fail every later publication. A pruning job therefore has to retire those assets in the counter ledger first, keeping the cumulative download totals. Until it does, REL-100's channel criterion stays unmet.

Immutable stable and nightly publication

Stable publication requires repository immutable releases enabled. All package, signature, checksum, SBOM, exact-CI and source/tag checks complete against the fully populated draft before the irreversible publication boundary. After publication the returned record must be immutable, and gh release verify verifies its automatic immutable-release attestation. A post-publication failure preserves an immutable or ambiguous target for owner investigation. If an exact fresh response instead proves that the same stable release ID, tag, and channel are public and mutable, the acceptance/recovery helper may quarantine that target as a draft and verify the result. It never infers permission to hide an immutable, misidentified, or ambiguous release. Either failure leaves final readiness blocked.

Nightly publication is immutable too. release.yml sets RELEASE_IMMUTABLE for both channels. Each nightly run creates a unique tag, nightly-<run>-<attempt>-<sha12> from nightly_release_tag.py, and never moves it. The legacy rolling nightly release is preserved and never mutated: its hide/redraft step is a disabled migration tombstone, and recover_release_publication.py returns without mutating a uniquely tagged nightly. The policy preflight runs inside the environment-bound publisher, and each ordinary publication/staging Git push or API write checks again in its own call path. The narrowly proven mutable-stable quarantine above is a containment exception, not permission to publish under an incompatible policy. Draft staging checks before each create, update, asset deletion, and upload, instead of delegating multiple unchecked mutations to a release action. Policy-flip tests prove that later writes are refused after a compatible policy changes. The legacy recovery writes that remain for the rolling nightly release still require a mutable policy and refuse otherwise. The workflow never toggles the repository policy automatically.

The migration to OD-17's target, uniquely tagged immutable nightlies, is in the workflow. No hosted run has yet published both channels under one immutable policy, so neither channel is described as operational. The REL-100 and REL-190 blockers stand until that evidence and the 30-day nightly pruning job exist.

Independent verification and final readiness

Final package and durable evidence boundaries

The required Windows Shipping job creates and checks a real portable ZIP and NSIS package, checks executable and first-party module DLL release versions, and records/verifies stable-v1 toolchain provenance from the configured tree. The archive reconciler extracts final ZIP/TGZ contents under bounded path rules and binds the inventory to the artifact digest and committed dependency lock.

Release publication retains the lock-derived SPDX SBOM, reconciliation reports, and per-build toolchain records as release assets. They participate in the expected asset inventory, checksums, signatures for stable releases, and build attestations. The independent stable consumer requires them. Consumer release attestation verification also runs for nightly releases.

Native runtime MSI/NSIS packages and standalone bootstrap installers do not yet have verified final payload inventories. SDK header/library reconciliation cannot be applied to their different layouts by inventing not-configured declarations. The final-package gate records this gap and refuses publication; an outer installer digest or pre-CPack stage does not satisfy it. Completing native extraction and the runtime/standalone inventory contract remains required, unless the owner explicitly changes the criterion's scope. No scope exemption is introduced by these tools.

The ready-site gate checks publication evidence against GitHub's completed release run and its attempt-specific jobs, the immutable stable tag, and the released commit's ancestry to the deployed source. Evidence-recording descendant commits are allowed. Offline validation separately requires the finalization gate and every profile's finalizer evidence to name the same released commit. These checks are tested locally; they do not constitute hosted publication evidence.

After publication, verify-stable-publication runs on a fresh runner with only Actions, contents, and attestations read permissions. It obtains the exact published asset IDs through GitHub, downloads the stable assets and the signature control asset, checks their sizes and SHA-256 digests, extracts and verifies the pinned detached signatures and SBOM, compares provenance to freshly revalidated exact-CI evidence, verifies the GitHub release attestation, and rechecks the release, assets, and tag for drift. Before downloading anything it rebuilds the approval record from the GitHub API for the publisher's attempt and requires the publisher's exact approval_record_sha256; the receipt embeds it. It cannot publish, edit the ledger, or turn the profile ready.

Only success produces the immutable Actions artifact stable-publication-evidence-<SHA>-<run>-<attempt>, retained for 90 days. The receipt says publication-verified, carries the candidate SHA, release and asset IDs/digests, and verifier run identity. Preserve that artifact and its GitHub artifact digest/producer identity in the release evidence archive before expiry; an unverified local JSON copy is not authority. A failed verifier run produces no success artifact, even if a late local publication error leaves a visible JSON file. Final readiness remains blocked pending investigation and recovery.

The receipt writer accepts at most 1 MiB and requires an existing real parent directory. It rejects existing names and symlink/reparse traversal. Linux publication anchors an anonymous O_TMPFILE inode to a directory descriptor, syncs the completed file, atomically links the owned fd without replacement, and syncs the directory before verifying the visible file and parent identities. The Linux-documented proc fd link mechanism needs no elevated capability. Missing O_TMPFILE, proc fd access, or directory sync support fails closed; other POSIX platforms have no named-temp fallback. Pre-link failure closes the anonymous inode, leaving no staging name to clean up. Post-link failure never deletes a visible name: it reports that publication or durability is unconfirmed, preserves any competing replacement, and requires investigation rather than issuing a successful receipt result. Windows retains the unchanged native package writer under locked parent directories. Neither backend overwrites an existing receipt.

The release owner must then reconcile this independent evidence, actual deployment approval, all supported-host/rehearsal results, and live-site consumption at the same released SHA. Only that completed review may close the declared finalization work and promote all required gates/profile/global state. --require-ready remains the final fail-closed validator. A successful publisher or consumer alone is insufficient. The released candidate commit is immutable; later evidence-recording commits must retain that exact artifact source identity.

First-release predecessor policy

The maintainer chose a signed, immutable v0.9.0 stable predecessor before v1.0.0, rather than a first-release bootstrap exception. The strict MSI qualifier must continue to require a real previously published, signed, non-prerelease stable MSI and its matching manifest. Publish v0.9.0 only after its own applicable signing, package, publication, and acceptance evidence is complete; then qualify the v1.0.0 upgrade, rollback, repair, and user-data retention gates against those exact immutable predecessor assets. The choice does not mark either release ready or waive any gate. Until v0.9.0 is independently verified as published, the provisioner correctly rejects v1.0.0. Fabricating an older release, treating nightly as the predecessor, or marking upgrade/rollback done without the real transaction is prohibited.

The maintainer chose to derive a reviewed v0.9.0 candidate from the current release branch in an isolated checkout after its source batch is sealed. That does not authorize relabeling the current 1.0.0 checkout in place or manufacturing an old tag: the candidate needs its own reviewed version and changelog commit, exact-source CI, signed artifacts, and publication evidence. The release controller requires the stable tag to match the single CMake version default and its changelog heading. No v0.9.0 source/tag or published release currently satisfies that rule. The existing versioned MSI job also always requires a prior release; it is intentionally not a v0.9.0 bootstrap path. Before any v0.9.0 dispatch, the owner must review and identify an authentic 0.9.0 source commit with corresponding version and changelog, and approve a separate one-time native qualification contract for a signed clean install, repair, runtime smoke, uninstall, and user-data retention without pretending to perform an upgrade or rollback. That contract must still pass the applicable exact-source CI, package, signing, protected-environment, immutable-publication, and independent-consumer gates. Only after the resulting v0.9.0 release is independently verified should the normal v1.0.0 gate run its actual old-to-new upgrade/rollback transaction. The normal provisioner pins v1.0.0 specifically to an immutable v0.9.0 release; a different earlier version or a mutable release cannot substitute for it.

Predecessor owner, baseline rule, and REL-190 substitution

Owner decisions OD-18 and OD-19 (2026-09-24) are recorded in predecessorRelease in docs/site/readiness.json:

  • In the v0.9.0 predecessor stage, REL-191 replaces REL-190 through qualificationSubstitutions, next to the existing REL-192→REL-191 and INST-131→INST-132 entries. REL-190 stays required for the stable-v1 release candidate and remains in both blocking lists. REL-191 already covers every common qualification gate and named sign-off without N-1 claims.
  • owner is Krilliac.
  • The baseline is not picked by hand. It is the first Working commit after the stable-v1 release branch merges at which every predecessorRelease.requiredGateIds gate has passing exact-SHA evidence. Until then sourceCommitEvidence.baselineCommit and signOffEvidence stay empty and the stage stays blocked; --require-predecessor-candidate rejects the empty baseline. test_release_stages.py checks that these fields stay empty while any required gate is not passing.

Experimental build ownership and contract proofs

tools/site-data/workflow_ownership.py binds experimental Shipping preset uses in workflow commands, including matrices and declared environment variables, to platform work or named runtime test hosts. Platform certification stays advisory and outside the Required CI Gate. The experimentalInstallerProducts registry in docs/site/readiness.json binds the actual nightly installer matrix artifacts to their experimental platform capabilities and work items; it does not add those installers to the stable profile.

build.yml/site-data-full-determinism compares two clean site-data generations, each regenerating documentation health independently. Dirty checkouts fail the proof. The existing advisory determinism job still checks its narrower bundle; only the required full proof covers independently generated health.

Release-profile SparkTests selectors inspect each test body and reachable local helpers for declarations from included production headers. Utility-only includes and constant assertions cannot establish production evidence. This source check is lexical: it does not prove C++ name resolution or assertion dependence, and raw whole-suite count floors remain a separate limitation. Module evidence also rejects copied or tautological bodies when deriving its expected test set.

Source & Freshness

Implemented 2026-09-21. Sources: readiness contract, release workflow, and GitHub environment API, and the repository immutability API. Predecessor identity and source-lineage review updated 2026-09-22; see the release API immutable field. Approval-event recording added 2026-09-24 against the workflow-run review history API. Recheck live environment protection, approval history, signing authority, and exact-SHA run/artifact identities before each release. Channel retention/support policy (OD-17) and the predecessor owner, baseline rule, and REL-190 substitution (OD-18/OD-19) recorded 2026-09-24 from owner decisions. Site-data status trust model updated 2026-10-01 from the publisher, the reference verifier, GitHub's workflow-run API, CORS guidance, and unauthenticated rate limits. Contract reference rules and the public numeric-claim ledger added 2026-09-24 from validate.py and test_site_data_contract.py. Generated stable release notes (REL-190) added 2026-09-24 from tools/release_notes.py. Nightly retention status and the immutable-nightly migration status (REL-100) updated 2026-09-28 from release.yml and download_counter_ledger.py. Release tag contract helpers (REL-100) added 2026-09-26 from stable_release_tag.py and nightly_release_tag.py.

SparkEngine Wiki

Website Entry Points

Getting Started

Engine Subsystems

Gameplay & Tools

Platform Support

Graphics

Advanced

Development & Process

Research & Analysis

Engineering Notes & Audits

Specifications

Reference

Clone this wiki locally