{"record":{"id":"0290456253f922e2","repo":"astrid-runtime/astrid","slug":"signed-channel-label-is-not-canonical-utc-rfc333","errorCode":null,"errorMessage":"signed channel {label} is not canonical UTC RFC3339 seconds","messagePattern":"signed channel (.+?) is not canonical UTC RFC3339 seconds","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/update_channel.rs","lineNumber":328,"sourceCode":"            .bytes()\n            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))\n}\n\nfn canonical_version(value: &str) -> anyhow::Result<semver::Version> {\n    let parsed = semver::Version::parse(value)\n        .with_context(|| format!(\"signed channel version '{value}' is not valid semver\"))?;\n    ensure!(\n        parsed.to_string() == value,\n        \"signed channel version '{value}' is not canonical semver\"\n    );\n    Ok(parsed)\n}\n\nfn canonical_time(value: &str, label: &str) -> anyhow::Result<DateTime<Utc>> {\n    let parsed = DateTime::parse_from_rfc3339(value)\n        .with_context(|| format!(\"signed channel {label} is not RFC3339\"))?\n        .with_timezone(&Utc);\n    ensure!(\n        parsed.to_rfc3339_opts(SecondsFormat::Secs, true) == value,\n        \"signed channel {label} is not canonical UTC RFC3339 seconds\"\n    );\n    Ok(parsed)\n}\n\nfn validate_targets_for(\n    targets: &[TargetMetadata],\n    expected_targets: &[&str],\n    version: &str,\n    label: &str,\n) -> anyhow::Result<()> {\n    ensure!(\n        targets.len() == expected_targets.len(),\n        \"{label} must contain exactly {} targets\",\n        expected_targets.len()\n    );\n    let mut seen = HashSet::new();","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/update_channel.rs#L310-L346","documentation":"`canonical_time` parses an RFC3339 timestamp with `chrono`, converts it to UTC, and requires that re-formatting it with `SecondsFormat::Secs` and `use_z=true` reproduces the input exactly. This error means the timestamp is parseable but not a canonical UTC RFC3339 second-precision value — e.g. it carries a non-UTC offset, fractional seconds, or an offset written as `+00:00` instead of `Z`. Signed channel pointers must use this canonical spelling so signatures are deterministic.","triggerScenarios":"`validate_pointer` receiving a timestamp field (labeled by `label`, e.g. valid-until/generated-at) like `2026-09-09T12:00:00.000Z`, `2026-09-09T14:00:00+02:00`, or `2026-09-09T12:00:00+00:00` instead of `2026-09-09T12:00:00Z`.","commonSituations":"A signing pipeline serializing timestamps with the local timezone or with millisecond precision; hand-written channel files; tooling using `to_rfc3339()` defaults (which emit `+00:00` and/or nanoseconds) instead of the canonical Z-terminated seconds form.","solutions":["Re-emit the timestamp in canonical form: UTC, second precision, `Z` suffix — e.g. `2026-09-09T12:00:00Z`.","Fix the generating code to use `DateTime<Utc>::to_rfc3339_opts(SecondsFormat::Secs, true)` (or equivalent) before signing.","Regenerate and re-sign the channel pointer, since the signature covers the raw bytes of the timestamp.","Convert any local-time timestamps to UTC first (keep the instant, change only the representation)."],"exampleFix":"// before\n\"expires\": \"2026-09-09T14:00:00.000+02:00\"\n\n// after (canonical UTC RFC3339 seconds)\n\"expires\": \"2026-09-09T12:00:00Z\"","handlingStrategy":"validation","validationCode":"// Rust: canonicalize before signing\nuse chrono::{DateTime, Utc, SecondsFormat};\nlet t: DateTime<Utc> = DateTime::parse_from_rfc3339(input)?.with_timezone(&Utc);\nassert_eq!(t.to_rfc3339_opts(SecondsFormat::Secs, true), input, \"timestamp must be canonical UTC RFC3339 seconds\");","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Always emit timestamps with `to_rfc3339_opts(SecondsFormat::Secs, true)`.","Convert to UTC before serializing; never serialize local time into channel metadata.","Avoid fractional seconds and `+00:00` offsets; use the `Z` suffix.","Add a signing-time assertion that the serialized form round-trips."],"tags":["datetime","rfc3339","validation","rust"],"backgroundTag":"invalid-date-format","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"}