# Excititor ยท Graph Linkouts & Overlays โ€” Implementation Notes (Graph 21-001/002/005/24-101/24-102) - **Date:** 2025-11-21 - **Scope:** EXCITITOR-GRAPH-21-001, EXCITITOR-GRAPH-21-002, EXCITITOR-GRAPH-21-005 - **Status:** Implementation guidance (storage wiring pending). ## Endpoints 1) **Linkouts (21-001)** - `POST /internal/graph/linkouts` - Body: `tenant`, `purls[]` (max 500), `includeJustifications?`, `includeProvenance?` - Response: ordered by input `purls`; each item includes `advisories[]` (`advisoryId`, `source`, `status`, `justification?`, `modifiedAt`, `evidenceHash`, `connectorId`, `dsseEnvelopeHash?`) plus `conflicts[]`; `notFound[]`. 2) **Overlays (21-002)** - `GET /v1/graph/overlays?purl=&purl=&includeJustifications=true|false` - Response per PURL: `summary` counts (`open`, `not_affected`, `under_investigation`, `no_statement`), `latestModifiedAt`, `justifications[]` (unique, sorted), `provenance` (`sources[]`, `lastEvidenceHash`), `cached`, `cacheAgeMs`. 3) **Status summaries (24-101)** - `GET /v1/graph/status?purl=&purl=` - Response mirrors overlay summaries but omits justification payloads; includes `sources[]`, `lastEvidenceHash`, `cached`, `cacheAgeMs`. Intended for Vuln Explorer status colouring. 4) **Batch observations for tooltips (24-102)** - `GET /v1/graph/observations?purl=[&purl=...]&includeJustifications=true|false[&limitPerPurl=50][&cursor=]` - Response per PURL: ordered `observations[]` (`observationId`, `advisoryId`, `status`, `justification?`, `providerId`, `modifiedAt`, `evidenceHash`, `dsseEnvelopeHash?`) plus `truncated`; top-level `nextCursor`, `hasMore` enable paging. Limits enforced per PURL and globally. ## Storage & Indexes (21-005) - `vex_observations` indexes: - `{ tenant: 1, component.purl: 1, advisoryId: 1, source: 1, modifiedAt: -1 }` - Sparse `{ tenant: 1, component.purl: 1, status: 1 }` - Optional materialized `vex_overlays` cache: unique `{ tenant: 1, purl: 1 }`, TTL on `cachedAt` driven by `excititor:graph:overlayTtlSeconds` (default 300s); payload must validate against `docs/modules/excititor/schemas/vex_overlay.schema.json` (schemaVersion 1.0.0). Bundle sample payload `docs/samples/excititor/vex-overlay-sample.json` in Offline Kits. ## Determinism - Ordering: input PURL order โ†’ `advisoryId` โ†’ `source` for linkouts; overlays follow input order. - Truncation: max 200 advisories per PURL; when truncated, include `truncated: true` and `nextCursor` (`advisoryId`, `source`). ## Config knobs - `excititor:graph:overlayTtlSeconds` (default 300) - `excititor:graph:maxPurls` (default 500) - `excititor:graph:maxAdvisoriesPerPurl` (default 200) - `excititor:graph:maxTooltipItemsPerPurl` (default 50) - `excititor:graph:maxTooltipTotal` (default 1000) ## Telemetry - Counter `excititor.graph.linkouts.requests` tags: `tenant`, `includeJustifications`, `includeProvenance`. - Counter `excititor.graph.overlays.cache` tags: `tenant`, `hit` (`true|false`). - Histogram `excititor.graph.linkouts.latency.ms` tags: `tenant`. ## Steps to implement - Bind `GraphOptions` to `Excititor:Graph`. - Add endpoints to WebService with tenant guard; enforce limits. - Implement overlay cache with deterministic sort; respect TTL; surface `cached` + `cacheAgeMs`. - Backfill Mongo indexes above. - Integration tests (WebApplicationFactory + Mongo2Go) for ordering, truncation, cache metadata, tenant isolation.