88 lines
4.9 KiB
Markdown
88 lines
4.9 KiB
Markdown
# 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)
|
|
```json
|
|
{
|
|
"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).
|