Files
git.stella-ops.org/docs/modules/excititor/graph-overlays.md

4.9 KiB

Excititor Graph Overlay Contract (v1.0.0)

Updated: 2025-12-10 | Owners: Excititor Core + UI Guilds | Scope: EXCITITOR-GRAPH-21-001..005, EXCITITOR-POLICY-20-001/002, EXCITITOR-RISK-66-001

Purpose

Defines the graph-ready overlay built from Link-Not-Merge observations/linksets so Console, Vuln Explorer, Policy, and Risk surfaces consume a single deterministic shape. This freezes the contract for Postgres materialization and cache APIs, unblocking Sprint 0120 tasks.

Schema

  • JSON Schema: docs/modules/excititor/schemas/vex_overlay.schema.json (draft 2020-12, schemaVersion 1.0.0).
  • Required fields: schemaVersion, generatedAt, tenant, purl, advisoryId, source, status, observations[], provenance.
  • Status enum: affected|not_affected|under_investigation|fixed|unknown.
  • Ordering: observations are sorted by source, advisoryId, fetchedAt (Link-Not-Merge invariant) and emitted in that order. Overlays are returned in request PURL order, then by advisoryId, then source.
  • Provenance: carries linksetId, linksetHash, observationHashes[], optional policyHash, sbomContextHash, and planCacheKey for replay.

Postgres materialization (IAppendOnlyLinksetStore)

  • Table vex_overlays (materialized cache):
    • Primary key: (tenant, purl, advisory_id, source).
    • Columns: status, justifications (jsonb), conflicts (jsonb), observations (jsonb), provenance (jsonb), cached_at, ttl_seconds, schema_version.
    • Indexes: unique (tenant, purl, advisory_id, source), plus (tenant, cached_at) for TTL sweeps.
  • Overlay rows are regenerated when linkset hash or observation hash set changes; cache evictions use cached_at + ttl_seconds.
  • Linksets and observation hashes come from the append-only linkset store (IAppendOnlyLinksetStore) to preserve Aggregation-Only Contract guarantees.

API shape (Graph/Vuln Explorer)

  • Endpoint: GET /v1/graph/overlays?purl=<purl>&purl=<purl>&includeJustifications=true|false.
  • Response items follow vex_overlay.schema.json; cache stanza signals cached, cachedAt, and ttlSeconds.
  • Cursoring: stable order (input PURL list) with nextPageToken based on (tenant, purl, advisoryId, source, generatedAt).
  • Telemetry: excititor.graph.overlays.cache{tenant,hit} counter; excititor.graph.overlays.latency_ms histogram tagged with cached.

Sample (abridged)

{
  "schemaVersion": "1.0.0",
  "generatedAt": "2025-12-10T00:00:00Z",
  "tenant": "tenant-default",
  "purl": "pkg:maven/org.example/foo@1.2.3",
  "advisoryId": "GHSA-xxxx-yyyy-zzzz",
  "source": "ghsa",
  "status": "affected",
  "justifications": [
    {
      "kind": "known_affected",
      "reason": "Upstream GHSA reports affected range <1.3.0.",
      "evidence": ["concelier:ghsa:obs:6561e41b3e3f4a6e9d3b91c1"],
      "weight": 0.8
    }
  ],
  "conflicts": [
    {
      "field": "affected.versions",
      "reason": "vendor_range_differs",
      "values": ["<1.2.0", "<=1.3.0"],
      "sourceIds": ["concelier:redhat:obs:...","concelier:ghsa:obs:..."]
    }
  ],
  "observations": [
    {
      "id": "concelier:ghsa:obs:6561e41b3e3f4a6e9d3b91c1",
      "contentHash": "sha256:1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd",
      "fetchedAt": "2025-11-19T00:00:00Z"
    }
  ],
  "provenance": {
    "linksetId": "concelier:ghsa:linkset:6561e41b3e3f4a6e9d3b91d0",
    "linksetHash": "sha256:deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead",
    "observationHashes": ["sha256:1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"],
    "policyHash": "sha256:0f7c...9ad3",
    "sbomContextHash": "sha256:421af53f9eeba6903098d292fbd56f98be62ea6130b5161859889bf11d699d18",
    "planCacheKey": "tenant-default|pkg:maven/org.example/foo@1.2.3|GHSA-xxxx-yyyy-zzzz"
  },
  "cache": {
    "cached": true,
    "cachedAt": "2025-12-10T00:00:00Z",
    "ttlSeconds": 300
  }
}

Validation & determinism

  • Validate overlays against vex_overlay.schema.json in CI and during materialization; reject or warn when fields drift.
  • Deterministic ordering: input PURL order, then advisoryId, then source; observation list sorted by source, advisoryId, fetchedAt.
  • No mutation: overlays are append-only; regeneration inserts a new row/version, leaving prior cache entries for audit until TTL expires.

Handoff

  • Consumers (Console, Vuln Explorer, Policy Engine, Risk) should treat vex_overlay.schema.json as the authoritative contract.
  • Offline kits must bundle the schema file and sample payloads under docs/samples/excititor/ with SHA256 manifests.
  • Future schema versions must bump schemaVersion and add migration notes to this document and docs/modules/excititor/architecture.md.
  • Policy and Risk surfaces in WebService now read overlays directly (with claim-store fallback for policy tests) to produce lookup and risk feeds; overlay cache/store are selected per tenant (in-memory by default, Postgres vex.graph_overlays when configured).