Files
git.stella-ops.org/docs/dev/merge_semver_playbook.md
master b97fc7685a
Some checks failed
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Initial commit (history squashed)
2025-10-11 23:28:35 +03:00

5.2 KiB
Raw Blame History

Feedser SemVer Merge Playbook (Sprint 12)

This playbook describes how the merge layer and connector teams should emit the new SemVer primitives introduced in Sprint12, how those primitives become normalized version rules, and how downstream jobs query them deterministically.

1. What landed in Sprint12

  • RangePrimitives.SemVer now infers a canonical style (range, exact, lt, lte, gt, gte) and captures exactValue when the constraint is a single version.
  • NormalizedVersionRule documents the analytics-friendly projection of each AffectedPackage coverage entry and is persisted alongside legacy versionRanges.
  • AdvisoryProvenance.decisionReason records whether merge resolution favored precedence, freshness, or a tie-breaker comparison.

See src/StellaOps.Feedser.Models/CANONICAL_RECORDS.md for the full schema and field descriptions.

2. Mapper pattern

Connectors should emit SemVer primitives as soon as they can normalize a vendor constraint. The helper SemVerPrimitiveExtensions.ToNormalizedVersionRule turns those primitives into the persisted rules:

var primitive = new SemVerPrimitive(
    introduced: "1.2.3",
    introducedInclusive: true,
    fixed: "2.0.0",
    fixedInclusive: false,
    lastAffected: null,
    lastAffectedInclusive: false,
    constraintExpression: ">=1.2.3 <2.0.0",
    exactValue: null);

var rule = primitive.ToNormalizedVersionRule(notes: "nvd:CVE-2025-1234");
// rule => scheme=semver, type=range, min=1.2.3, minInclusive=true, max=2.0.0, maxInclusive=false

Emit the resulting rule inside AffectedPackage.NormalizedVersions while continuing to populate AffectedVersionRange.RangeExpression for backward compatibility.

3. Merge dedupe flow

During merge, feed all package candidates through NormalizedVersionRuleComparer.Instance prior to persistence. The comparer orders by scheme → type → min → minInclusive → max → maxInclusive → value → notes, guaranteeing consistent document layout and making $unwind pipelines deterministic.

If multiple connectors emit identical constraints, the merge layer should:

  1. Combine provenance entries (preserving one per source).
  2. Preserve a single normalized rule instance (thanks to NormalizedVersionRuleEqualityComparer.Instance).
  3. Attach decisionReason="precedence" if one source overrides another.

4. Example Mongo pipeline

Use the following aggregation to locate advisories that affect a specific SemVer:

db.advisories.aggregate([
  { $match: { "affectedPackages.type": "semver", "affectedPackages.identifier": "pkg:npm/lodash" } },
  { $unwind: "$affectedPackages" },
  { $unwind: "$affectedPackages.normalizedVersions" },
  { $match: {
      $or: [
        { "affectedPackages.normalizedVersions.type": "exact",
          "affectedPackages.normalizedVersions.value": "4.17.21" },
        { "affectedPackages.normalizedVersions.type": "range",
          "affectedPackages.normalizedVersions.min": { $lte: "4.17.21" },
          "affectedPackages.normalizedVersions.max": { $gt: "4.17.21" } },
        { "affectedPackages.normalizedVersions.type": "gte",
          "affectedPackages.normalizedVersions.min": { $lte: "4.17.21" } },
        { "affectedPackages.normalizedVersions.type": "lte",
          "affectedPackages.normalizedVersions.max": { $gte: "4.17.21" } }
      ]
  }},
  { $project: { advisoryKey: 1, title: 1, "affectedPackages.identifier": 1 } }
]);

Pair this query with the indexes listed in Normalized Versions Query Guide.

Collection Index Purpose
advisory { "affectedPackages.identifier": 1, "affectedPackages.normalizedVersions.scheme": 1, "affectedPackages.normalizedVersions.type": 1 } (compound, multikey) Speeds up $match on identifier + rule style.
advisory { "affectedPackages.normalizedVersions.value": 1 } (sparse) Optimizes lookups for exact version hits.

Coordinate with the Storage team when enabling these indexes so deployment windows account for collection size.

6. Dual-write rollout

Follow the operational checklist in docs/ops/migrations/SEMVER_STYLE.md. The summary:

  1. Dual write (now) emit both legacy versionRanges and the new normalizedVersions.
  2. Backfill follow the storage migration in docs/ops/migrations/SEMVER_STYLE.md to rewrite historical advisories before switching consumers.
  3. Verify run the aggregation above (with explain("executionStats")) to ensure the new indexes are used.
  4. Cutover after consumers switch to normalized rules, mark the old rangeExpression as deprecated.

7. Checklist for connectors & merge

  • Populate SemVerPrimitive for every SemVer-friendly constraint.
  • Call ToNormalizedVersionRule and store the result.
  • Emit provenance masks covering both versionRanges[].primitives.semver and normalizedVersions[].
  • Ensure merge deduping relies on the canonical comparer.
  • Capture merge decisions via decisionReason.
  • Confirm integration tests include fixtures with normalized rules and SemVer styles.

For deeper query examples and maintenance tasks, continue with Normalized Versions Query Guide.