Files
git.stella-ops.org/docs/dev/normalized-rule-recipes.md
Vladimir Moushkov 55464f8498
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
up
2025-10-29 19:24:20 +02:00

5.2 KiB
Raw Blame History

Normalized Version Rule Recipes

Status: 2025-10-29

This guide captures the minimum wiring required for connectors and Merge coordination tasks to finish the normalized-version rollout that unblocks FEEDMERGE-COORD-02-9xx.

1. Quick-start checklist

  1. Ensure your mapper already emits AffectedPackage.VersionRanges (SemVer, NEVRA, EVR). If you only have vendor/product strings, capture the raw range text before trimming so it can feed the helper.
  2. Call SemVerRangeRuleBuilder.BuildNormalizedRules(rawRange, patchedVersion, provenance) for each range and place the result in AffectedPackage.NormalizedVersions.
  3. Set a provenance note in the format connector:{advisoryId}:{index} so Merge can differentiate connector-provided rules from canonical fallbacks.
  4. Verify with dotnet test that the connector snapshot fixtures now include the normalizedVersions array and update fixtures by setting the connector-specific UPDATE_*_FIXTURES=1 environment variable.
  5. Tail Merge logs (or the test output) for the new warning Normalized version rules missing for {AdvisoryKey}; an empty warning stream means the connector/merge artefacts are ready to close FEEDMERGE-COORD-02-901/902.

2. Code snippet: SemVer connector (CCCS/Cisco/ICS-CISA)

using StellaOps.Concelier.Normalization.SemVer;

private static IReadOnlyList<AffectedPackage> BuildPackages(MyDto dto, DateTimeOffset recordedAt)
{
    var packages = new List<AffectedPackage>();

    foreach (var entry in dto.AffectedEntries.Select((value, index) => (value, index)))
    {
        var rangeText = entry.value.Range?.Trim();
        var patched = entry.value.FixedVersion;
        var provenance = $"{MyConnectorPlugin.SourceName}:{dto.AdvisoryId}:{entry.index}";

        var normalizedRules = SemVerRangeRuleBuilder.BuildNormalizedRules(rangeText, patched, provenance);
        var primitives = SemVerRangeRuleBuilder.Build(rangeText, patched, provenance)
            .Select(result => result.Primitive.ToAffectedVersionRange(provenance))
            .ToArray();

        packages.Add(new AffectedPackage(
            AffectedPackageTypes.SemVer,
            entry.value.PackageId,
            versionRanges: primitives,
            normalizedVersions: normalizedRules,
            provenance: new[]
            {
                new AdvisoryProvenance(
                    MyConnectorPlugin.SourceName,
                    "package",
                    entry.value.PackageId,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.AffectedPackages })
            }));
    }

    return packages;
}

A few notes:

  • If you already have SemVerPrimitive instances, call .ToNormalizedVersionRule(provenance) on each primitive instead of rebuilding from raw strings.
  • Use SemVerRangeRuleBuilder.BuildNormalizedRules when the connector only tracks raw range text plus an optional fixed/patched version.
  • For products that encode ranges like "ExampleOS 4.12 - 4.14", run a small regex to peel off the version substring (see §3) and use the same provenance note when emitting the rule and the original range primitive.

3. Parsing helper for trailing version phrases

Many of the overdue connectors store affected products as natural-language phrases. The following helper normalises common patterns (1.2 - 1.4, <= 3.5, Version 7.2 and later).

private static string? TryExtractRangeSuffix(string productString)
{
    if (string.IsNullOrWhiteSpace(productString))
    {
        return null;
    }

    var match = Regex.Match(productString, "(?<range>(?:<=?|>=?)?\s*\d+(?:\.\d+){0,2}(?:\s*-\s*\d+(?:\.\d+){0,2})?)", RegexOptions.CultureInvariant);
    return match.Success ? match.Groups["range"].Value.Trim() : null;
}

Once you extract the range fragment, feed it to SemVerRangeRuleBuilder.BuildNormalizedRules(range, null, provenance). Keep the original product string as-is so operators can still see the descriptive text.

4. Merge dashboard hygiene

  • Run dotnet build src/Concelier/__Libraries/StellaOps.Concelier.Merge/StellaOps.Concelier.Merge.csproj after wiring a connector to confirm no warnings appear.
  • Merge counter tag pairs to watch in Grafana/CI logs:
    • concelier.merge.normalized_rules{package_type="npm"} increases once the connector emits normalized arrays.
    • concelier.merge.normalized_rules_missing{package_type="vendor"} should trend to zero once rollout completes.
  • The Merge service now logs Normalized version rules missing for {AdvisoryKey}; sources=...; packageTypes=... when a connector still needs to supply normalized rules. Use this as the acceptance gate for FEEDMERGE-COORD-02-901/902.

5. Documentation touchpoints

  • Update the connector TASKS.md entry with the date you flipped on normalized rules and note the provenance format you chose.
  • Record any locale-specific parsing (e.g., German bis) in the connector README so future contributors can regenerate fixtures confidently.
  • When opening the PR, include dotnet test output covering the connector tests so reviewers see the normalized array diff.

Once each connector follows the steps above, we can mark FEEDCONN-CCCS-02-009, FEEDCONN-CISCO-02-009, FEEDCONN-CERTBUND-02-010, FEEDCONN-ICSCISA-02-012, and the FEEDMERGE-COORD-02-90x tasks as resolved.