Files
git.stella-ops.org/docs/modules/concelier/api/lnm-linksets.md

7.5 KiB

Link-Not-Merge Linksets API (v1)

Status: stable; frozen 2025-11-17 per LNM v1 spec.

Intent

  • Provide fact-only advisory linkset retrieval for Policy Engine, graph overlays, and console clients.
  • Preserve provenance and tenant isolation; results are deterministically ordered and stable for identical queries.
  • Surface conflicts between observations without resolving them (Link-Not-Merge philosophy).

Endpoints

List Linksets

  • Method: GET
  • Path: /v1/lnm/linksets

Get Linkset by ID

  • Method: GET
  • Path: /v1/lnm/linksets/{advisoryId}

Search Linksets

  • Method: POST
  • Path: /v1/lnm/linksets/search

Headers

Header Required Description
X-Stella-Tenant Yes Tenant identifier for multi-tenant isolation.
X-Stella-Request-Id No Optional correlation ID for distributed tracing.

Query Parameters (GET)

Parameter Type Description
purl string[] Filter by Package URLs (repeatable).
cpe string Filter by CPE identifier.
cve string Filter by CVE identifier.
ghsa string Filter by GHSA identifier.
advisoryId string Filter by advisory ID.
source string Filter by upstream source.
severityMin float Minimum severity score.
severityMax float Maximum severity score.
publishedSince datetime Published after this timestamp.
modifiedSince datetime Modified after this timestamp.
includeConflicts boolean Include conflict details (default: true).
includeObservations boolean Include observation IDs (default: false).
page integer Page number (default: 1).
pageSize integer Items per page (default: 50, max: 200).
sort string Sort order (see sorting section).
{
  "purl": ["pkg:npm/lodash@4.17.20"],
  "includeConflicts": true,
  "includeObservations": true,
  "pageSize": 10
}

Response (200)

{
  "items": [
    {
      "advisoryId": "CVE-2021-23337",
      "source": "nvd",
      "purl": ["pkg:npm/lodash@4.17.20"],
      "cpe": ["cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:node.js:*:*"],
      "summary": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.",
      "publishedAt": "2021-02-15T13:15:00Z",
      "modifiedAt": "2024-08-04T19:16:00Z",
      "severity": "high",
      "status": "affected",
      "provenance": {
        "ingestedAt": "2025-11-20T10:30:00Z",
        "connectorId": "nvd-osv-connector",
        "evidenceHash": "sha256:a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
        "dsseEnvelopeHash": null
      },
      "conflicts": [
        {
          "field": "severity",
          "reason": "severity-mismatch",
          "observedValue": "critical",
          "observedAt": "2025-11-18T08:00:00Z",
          "evidenceHash": "sha256:f6e5d4c3b2a1098765432109876543210fedcba0987654321fedcba098765432"
        }
      ],
      "timeline": [
        {
          "event": "observed",
          "at": "2025-11-15T10:00:00Z",
          "evidenceHash": "sha256:a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
        },
        {
          "event": "conflict-detected",
          "at": "2025-11-18T08:00:00Z",
          "evidenceHash": "sha256:f6e5d4c3b2a1098765432109876543210fedcba0987654321fedcba098765432"
        }
      ],
      "normalized": {
        "aliases": ["CVE-2021-23337", "GHSA-35jh-r3h4-6jhm"],
        "purl": ["pkg:npm/lodash@4.17.20"],
        "cpe": ["cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:node.js:*:*"],
        "versions": ["4.17.20"],
        "ranges": [
          {
            "type": "SEMVER",
            "events": [
              {"introduced": "0.0.0"},
              {"fixed": "4.17.21"}
            ]
          }
        ],
        "severities": [
          {"type": "CVSS_V3", "score": 7.2}
        ]
      },
      "cached": false,
      "remarks": [],
      "observations": ["obs:nvd:CVE-2021-23337:2025-11-15", "obs:github:GHSA-35jh-r3h4-6jhm:2025-11-18"]
    }
  ],
  "page": 1,
  "pageSize": 10,
  "total": 1
}

Response (Air-gapped deployment)

When deployed in air-gapped mode, responses include freshness metadata:

{
  "items": [
    {
      "advisoryId": "CVE-2021-23337",
      "source": "nvd",
      "purl": ["pkg:npm/lodash@4.17.20"],
      "cpe": [],
      "provenance": {
        "ingestedAt": "2025-11-20T10:30:00Z",
        "connectorId": "offline-bundle-importer",
        "evidenceHash": "sha256:a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
      },
      "conflicts": [],
      "cached": true,
      "freshness": {
        "staleness": {
          "lastRefreshedAt": "2025-11-20T10:30:00Z",
          "ageSeconds": 86400,
          "isStale": false,
          "thresholdSeconds": 172800,
          "status": "fresh"
        },
        "bundleProvenance": {
          "bundleId": "offline-2025-11-20",
          "bundleVersion": "1.0.0",
          "sourceId": "nvd-mirror",
          "importedAt": "2025-11-20T10:30:00Z",
          "contentHash": "sha256:bundle-hash-here",
          "signatureStatus": "verified",
          "signatureKeyId": "key:stellaops:offline-signing:2025",
          "isAirGapped": true
        },
        "computedAt": "2025-11-21T10:30:00Z"
      }
    }
  ],
  "page": 1,
  "pageSize": 10,
  "total": 1
}

Errors

Status Code Description
400 ERR_VALIDATION_FAILED Invalid query parameters or request body.
400 ERR_PAGE_SIZE_EXCEEDED Page size exceeds maximum of 200.
401 ERR_UNAUTHORIZED Missing or invalid authentication.
403 ERR_FORBIDDEN Tenant access denied.
404 ERR_RESOURCE_NOT_FOUND Linkset not found (for GET by ID).
429 ERR_RATE_LIMITED Too many requests; check Retry-After header.

Error Response Example

{
  "type": "https://stellaops.io/errors/validation-failed",
  "title": "Validation Failed",
  "status": 400,
  "detail": "The 'pageSize' parameter exceeds the maximum allowed value of 200.",
  "instance": "/v1/lnm/linksets",
  "traceId": "trace-id-abc123",
  "error": {
    "code": "ERR_PAGE_SIZE_EXCEEDED",
    "message": "Page size must be between 1 and 200.",
    "target": "pageSize",
    "metadata": {
      "provided": 500,
      "maximum": 200
    }
  }
}

Sorting

Available sort options:

  • modifiedAt desc (default)
  • modifiedAt asc
  • publishedAt desc
  • publishedAt asc
  • severity desc
  • severity asc
  • source
  • advisoryId

Tie-breaking: when primary sort values are equal, results are ordered by advisoryId asc, then source asc.

Determinism & Caching

  • All results are deterministically ordered based on sort parameters.
  • Timestamps are UTC ISO-8601 format.
  • Hashes are lowercase hex with algorithm prefix (e.g., sha256:).
  • Cache key includes: tenant|filters|sort|page|pageSize.
  • Cache headers: X-Stella-Cache-Hit, X-Stella-Cache-Key.

Notes

  • Linksets represent the current aggregate state of all observations for an advisory.
  • Conflicts are surfaced but not resolved; consumers must implement their own conflict resolution strategy.
  • The normalized field contains processed data suitable for version matching and range evaluation.
  • Observation IDs in the observations array can be used to fetch raw observation details via the observations API.

Changelog

  • 2025-12-06: Added curated examples with conflict and air-gap scenarios (CONCELIER-WEB-OAS-62-001).
  • 2025-11-17: LNM v1 specification frozen.