{"record":{"id":"bc2001a3d109c31e","repo":"astrid-runtime/astrid","slug":"stable-and-dev-channels-must-point-to-canonical-re","errorCode":null,"errorMessage":"stable and dev channels must point to canonical releases","messagePattern":"stable and dev channels must point to canonical releases","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/update_channel.rs","lineNumber":508,"sourceCode":"        );\n    }\n    let max_lifetime = match expected_channel {\n        UpdateChannel::Stable => chrono::Duration::days(30),\n        UpdateChannel::Dev => chrono::Duration::days(7),\n        UpdateChannel::Nightly => chrono::Duration::days(2),\n    };\n    ensure!(\n        expires.signed_duration_since(published) <= max_lifetime,\n        \"signed channel lifetime exceeds the maximum for its channel\"\n    );\n    let version = canonical_version(&pointer.release.version)?;\n    let nightly_commit = nightly_source_commit(&version);\n    match expected_channel {\n        UpdateChannel::Nightly => ensure!(\n            nightly_commit.is_some() && version.build.is_empty(),\n            \"nightly channel must point to an exact nightly prerelease\"\n        ),\n        UpdateChannel::Stable | UpdateChannel::Dev => ensure!(\n            version.pre.is_empty() && version.build.is_empty(),\n            \"stable and dev channels must point to canonical releases\"\n        ),\n    }\n    ensure!(\n        pointer.release.tag == format!(\"v{version}\"),\n        \"signed channel release tag does not match its version\"\n    );\n    ensure!(\n        is_commit(&pointer.release.source_commit),\n        \"signed channel source commit is invalid\"\n    );\n    if let Some(commit) = nightly_commit {\n        ensure!(\n            commit == pointer.release.source_commit,\n            \"nightly channel version does not embed its source commit\"\n        );\n    }","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/update_channel.rs#L490-L526","documentation":"Stable and dev channel pointers must reference canonical releases: the version must have an empty pre-release segment and empty build metadata. A pointer on these channels to something like `1.2.3-rc.1` or `1.2.3+build5` is rejected because stable/dev distributions must come from final, unadorned versions.","triggerScenarios":"parse_channel or enforce_continuity with expected_channel = Stable or Dev and a version containing `version.pre` (e.g. `2.0.0-rc.1`, `0.5.0-nightly.20260909.deadbeef`) or non-empty `version.build`.","commonSituations":"Copying a nightly pointer into the dev channel during testing; publishing an RC to the stable channel; a release script forgetting to strip build metadata after the final tag.","solutions":["Point the stable/dev channel at the final release version (e.g. `1.2.3`, no suffix) and republish","Publish pre-releases under the nightly channel, or don't publish them as channel pointers at all","Strip build metadata when promoting a tested build to stable, then cut a proper release tag","Re-run the release workflow for the correct channel"],"exampleFix":"// before (dev channel.toml)\nversion = \"1.2.3-rc.1\"\n// after\nversion = \"1.2.3\"","handlingStrategy":"validation","validationCode":"fn stable_or_dev_version_ok(v: &semver::Version) -> bool {\n    v.pre.is_empty() && v.build.is_empty()\n}","typeGuard":"fn is_canonical_release(version: &str) -> bool {\n    semver::Version::parse(version)\n        .map(|v| v.pre.is_empty() && v.build.is_empty())\n        .unwrap_or(false)\n}","tryCatchPattern":"match parse_channel(&bytes, UpdateChannel::Stable, now) {\n    Err(e) if e.to_string().contains(\"canonical releases\") => {\n        anyhow::bail!(\"stable channel must not point at a prerelease; publish the final version\")\n    }\n    other => other?,\n}","preventionTips":["Only promote fully released (no pre/build suffix) versions to stable/dev","Publish RCs and nightlies solely under the nightly channel","Add a pre-publish lint asserting version.pre.is_empty() && version.build.is_empty() for these channels"],"tags":["update-channel","semver","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}