consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -0,0 +1,125 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stellaops.dev/schemas/excititor/connector-signer-metadata.schema.json",
"title": "Excititor Connector Signer Metadata",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "generatedAt", "connectors"],
"properties": {
"schemaVersion": {
"type": "string",
"pattern": "^1\\.0\\.0$"
},
"generatedAt": {
"type": "string",
"format": "date-time"
},
"connectors": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/connector"
}
}
},
"$defs": {
"connector": {
"type": "object",
"additionalProperties": false,
"required": [
"connectorId",
"provider",
"issuerTier",
"signers"
],
"properties": {
"connectorId": {
"type": "string",
"pattern": "^[a-z0-9:-\\.]+$"
},
"provider": {
"type": "object",
"additionalProperties": false,
"required": ["name", "slug"],
"properties": {
"name": { "type": "string", "minLength": 3 },
"slug": { "type": "string", "pattern": "^[a-z0-9-]+$" }
}
},
"issuerTier": {
"type": "string",
"enum": ["tier-0", "tier-1", "tier-2", "untrusted"]
},
"signers": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/signer" }
},
"bundle": { "$ref": "#/$defs/bundleRef" },
"validFrom": { "type": "string", "format": "date" },
"validTo": { "type": "string", "format": "date" },
"revoked": { "type": "boolean", "default": false },
"notes": { "type": "string", "maxLength": 2000 }
}
},
"signer": {
"type": "object",
"additionalProperties": false,
"required": ["usage", "fingerprints"],
"properties": {
"usage": {
"type": "string",
"enum": ["csaf", "oval", "openvex", "bulk-meta", "attestation"]
},
"fingerprints": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/fingerprint" }
},
"keyLocator": {
"type": "string",
"description": "Path or URL (mirror/OCI/TUF) where the signing key or certificate chain can be retrieved in offline kits."
},
"certificateChain": {
"type": "array",
"items": { "type": "string" },
"description": "Optional PEM-encoded certificates for x509/cosign keys."
}
}
},
"fingerprint": {
"type": "object",
"additionalProperties": false,
"required": ["alg", "value"],
"properties": {
"alg": {
"type": "string",
"enum": ["sha256", "sha512", "sha1"]
},
"format": {
"type": "string",
"enum": ["pgp", "x509-spki", "x509-ski", "cosign", "pem"]
},
"value": {
"type": "string",
"minLength": 16,
"maxLength": 128
}
}
},
"bundleRef": {
"type": "object",
"additionalProperties": false,
"required": ["kind", "uri"],
"properties": {
"kind": {
"type": "string",
"enum": ["oci-referrer", "oci-tag", "file", "tuf"]
},
"uri": { "type": "string", "minLength": 8 },
"digest": { "type": "string", "minLength": 32 },
"publishedAt": { "type": "string", "format": "date-time" }
}
}
}
}

View File

@@ -0,0 +1,305 @@
# Issuer Directory Contract v1.0.0
**Status:** APPROVED
**Version:** 1.0.0
**Effective:** 2025-12-19
**Owner:** VEX Lens Guild + Issuer Directory Guild
**Sprint:** SPRINT_0129_0001_0001 (unblocks VEXLENS-30-003)
---
## 1. Purpose
The Issuer Directory provides a registry of known VEX statement issuers with trust metadata, signing key information, and provenance tracking.
## 2. Data Model
### 2.1 Issuer Entity
```csharp
public sealed record Issuer
{
/// <summary>Unique issuer identifier (e.g., "vendor:redhat", "cert:cisa").</summary>
public required string IssuerId { get; init; }
/// <summary>Issuer category.</summary>
public required IssuerCategory Category { get; init; }
/// <summary>Display name.</summary>
public required string DisplayName { get; init; }
/// <summary>Trust tier assignment.</summary>
public required IssuerTrustTier TrustTier { get; init; }
/// <summary>Official website URL.</summary>
public string? WebsiteUrl { get; init; }
/// <summary>Security advisory feed URL.</summary>
public string? AdvisoryFeedUrl { get; init; }
/// <summary>Registered signing keys.</summary>
public ImmutableArray<SigningKeyInfo> SigningKeys { get; init; }
/// <summary>Products/ecosystems this issuer is authoritative for.</summary>
public ImmutableArray<string> AuthoritativeFor { get; init; }
/// <summary>When this issuer record was created.</summary>
public DateTimeOffset CreatedAt { get; init; }
/// <summary>When this issuer record was last updated.</summary>
public DateTimeOffset UpdatedAt { get; init; }
/// <summary>Whether issuer is active.</summary>
public bool IsActive { get; init; } = true;
}
```
### 2.2 Issuer Category
```csharp
public enum IssuerCategory
{
/// <summary>Software vendor/maintainer.</summary>
Vendor = 0,
/// <summary>Linux distribution.</summary>
Distribution = 1,
/// <summary>CERT/security response team.</summary>
Cert = 2,
/// <summary>Security research organization.</summary>
SecurityResearch = 3,
/// <summary>Community project.</summary>
Community = 4,
/// <summary>Commercial security vendor.</summary>
Commercial = 5
}
```
### 2.3 Signing Key Info
```csharp
public sealed record SigningKeyInfo
{
/// <summary>Key fingerprint (SHA-256).</summary>
public required string Fingerprint { get; init; }
/// <summary>Key type (pgp, x509, sigstore).</summary>
public required string KeyType { get; init; }
/// <summary>Key algorithm (rsa, ecdsa, ed25519).</summary>
public string? Algorithm { get; init; }
/// <summary>Key size in bits.</summary>
public int? KeySize { get; init; }
/// <summary>Key creation date.</summary>
public DateTimeOffset? CreatedAt { get; init; }
/// <summary>Key expiration date.</summary>
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>Whether key is currently valid.</summary>
public bool IsValid { get; init; } = true;
/// <summary>Public key location (URL or inline).</summary>
public string? PublicKeyUri { get; init; }
}
```
## 3. Pre-Registered Issuers
### 3.1 Authoritative Tier (Trust Tier 0)
| Issuer ID | Display Name | Category | Authoritative For |
|-----------|--------------|----------|-------------------|
| `vendor:redhat` | Red Hat Product Security | Vendor | `pkg:rpm/redhat/*`, `pkg:oci/registry.redhat.io/*` |
| `vendor:canonical` | Ubuntu Security Team | Distribution | `pkg:deb/ubuntu/*` |
| `vendor:debian` | Debian Security Team | Distribution | `pkg:deb/debian/*` |
| `vendor:suse` | SUSE Security Team | Distribution | `pkg:rpm/suse/*`, `pkg:rpm/opensuse/*` |
| `vendor:microsoft` | Microsoft Security Response | Vendor | `pkg:nuget/*` (Microsoft packages) |
| `vendor:oracle` | Oracle Security | Vendor | `pkg:maven/com.oracle.*/*` |
| `vendor:apache` | Apache Security Team | Community | `pkg:maven/org.apache.*/*` |
| `vendor:google` | Google Security Team | Vendor | `pkg:golang/google.golang.org/*` |
### 3.2 Trusted Tier (Trust Tier 1)
| Issuer ID | Display Name | Category |
|-----------|--------------|----------|
| `cert:cisa` | CISA | Cert |
| `cert:nist` | NIST NVD | Cert |
| `cert:github` | GitHub Security Advisories | SecurityResearch |
| `cert:snyk` | Snyk Security | Commercial |
| `research:oss-fuzz` | Google OSS-Fuzz | SecurityResearch |
### 3.3 Community Tier (Trust Tier 2)
| Issuer ID | Display Name | Category |
|-----------|--------------|----------|
| `community:osv` | OSV (Open Source Vulnerabilities) | Community |
| `community:vulndb` | VulnDB | Community |
## 4. API Endpoints
### 4.1 List Issuers
```
GET /api/v1/issuers
```
Query Parameters:
- `category`: Filter by category
- `trust_tier`: Filter by trust tier
- `active`: Filter by active status (default: true)
- `limit`: Max results (default: 100)
- `cursor`: Pagination cursor
### 4.2 Get Issuer
```
GET /api/v1/issuers/{issuerId}
```
### 4.3 Register Issuer (Admin)
```
POST /api/v1/issuers
Authorization: Bearer {admin_token}
{
"issuerId": "vendor:acme",
"category": "vendor",
"displayName": "ACME Security",
"trustTier": "trusted",
"websiteUrl": "https://security.acme.example",
"advisoryFeedUrl": "https://security.acme.example/feed.json",
"authoritativeFor": ["pkg:npm/@acme/*"]
}
```
### 4.4 Register Signing Key (Admin)
```
POST /api/v1/issuers/{issuerId}/keys
Authorization: Bearer {admin_token}
{
"fingerprint": "sha256:abc123...",
"keyType": "pgp",
"algorithm": "rsa",
"keySize": 4096,
"publicKeyUri": "https://security.acme.example/keys/signing.asc"
}
```
### 4.5 Lookup by Fingerprint
```
GET /api/v1/issuers/by-fingerprint/{fingerprint}
```
Returns the issuer associated with a signing key fingerprint.
## 5. Trust Tier Resolution
### 5.1 Automatic Assignment
When a VEX statement is received:
1. **Check signature:** If signed, lookup issuer by key fingerprint
2. **Check domain:** Match issuer by advisory feed domain
3. **Check authoritativeFor:** Match issuer by product PURL patterns
4. **Fallback:** Assign `Unknown` tier if no match
### 5.2 Override Rules
Operators can configure trust overrides:
```yaml
# etc/vexlens.yaml
issuer_overrides:
- issuer_id: "community:custom-feed"
trust_tier: "trusted" # Promote community to trusted
- issuer_id: "vendor:untrusted-vendor"
trust_tier: "community" # Demote vendor to community
```
## 6. Issuer Verification
### 6.1 PGP Signature Verification
```csharp
public interface IIssuerVerifier
{
/// <summary>
/// Verifies a VEX document signature against registered issuer keys.
/// </summary>
Task<IssuerVerificationResult> VerifyAsync(
byte[] documentBytes,
byte[] signatureBytes,
CancellationToken cancellationToken = default);
}
public sealed record IssuerVerificationResult
{
public bool IsValid { get; init; }
public string? IssuerId { get; init; }
public string? KeyFingerprint { get; init; }
public IssuerTrustTier? TrustTier { get; init; }
public string? VerificationError { get; init; }
}
```
### 6.2 Sigstore Verification
For Sigstore-signed documents:
1. Verify Rekor inclusion proof
2. Extract OIDC identity from certificate
3. Match identity to registered issuer
4. Return issuer info with trust tier
## 7. Database Schema
```sql
CREATE TABLE vex.issuers (
issuer_id TEXT PRIMARY KEY,
category TEXT NOT NULL,
display_name TEXT NOT NULL,
trust_tier INT NOT NULL DEFAULT 3,
website_url TEXT,
advisory_feed_url TEXT,
authoritative_for TEXT[] DEFAULT '{}',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE vex.issuer_signing_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issuer_id TEXT NOT NULL REFERENCES vex.issuers(issuer_id),
fingerprint TEXT NOT NULL UNIQUE,
key_type TEXT NOT NULL,
algorithm TEXT,
key_size INT,
public_key_uri TEXT,
is_valid BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_issuer_signing_keys_fingerprint ON vex.issuer_signing_keys(fingerprint);
CREATE INDEX idx_issuers_trust_tier ON vex.issuers(trust_tier);
```
---
## Changelog
| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | 2025-12-19 | Initial release |

View File

@@ -0,0 +1,82 @@
openapi: 3.1.0
info:
title: StellaOps Excititor Chunk API
version: "0.1.0"
description: |
Frozen for Sprint 110 (EXCITITOR-AIAI-31-002). Aligns with Evidence Locker attestation contract v1.
servers:
- url: https://excitor.local
paths:
/vex/evidence/chunks:
post:
summary: Submit VEX evidence chunk (aggregation-only)
requestBody:
required: true
content:
application/x-ndjson:
schema:
$ref: '#/components/schemas/VexChunk'
responses:
'202':
description: Accepted for processing
content:
application/json:
schema:
type: object
required: [chunk_digest, queue_id]
properties:
chunk_digest:
type: string
description: sha256 of canonical chunk JSON
queue_id:
type: string
description: Background job identifier
'400':
description: Validation error
components:
schemas:
VexChunk:
type: object
required: [chunk_id, tenant, source, schema, items, provenance]
properties:
chunk_id:
type: string
format: uuid
tenant:
type: string
source:
type: string
description: feed id (e.g., ghsa, nvd)
schema:
type: string
enum: [stellaops.vex.chunk.v1]
items:
type: array
items:
type: object
required: [advisory_id, status, purl]
properties:
advisory_id:
type: string
status:
type: string
enum: [affected, unaffected, under_investigation, fixed, unknown]
purl:
type: string
justification:
type: string
last_observed:
type: string
format: date-time
provenance:
type: object
required: [fetched_at, artifact_sha]
properties:
fetched_at:
type: string
format: date-time
artifact_sha:
type: string
signature:
type: object
nullable: true

View File

@@ -0,0 +1,271 @@
# VEX Normalization Contract v1.0.0
**Status:** APPROVED
**Version:** 1.0.0
**Effective:** 2025-12-19
**Owner:** VEX Lens Guild
**Sprint:** SPRINT_0129_0001_0001 (unblocks VEXLENS-30-001 through 30-011)
---
## 1. Purpose
This contract defines the normalization rules for VEX (Vulnerability Exploitability eXchange) documents from multiple sources into a canonical StellaOps internal representation.
## 2. Supported Input Formats
| Format | Version | Parser |
|--------|---------|--------|
| OpenVEX | 0.2.0+ | `OpenVexParser` |
| CycloneDX VEX | 1.5+ | `CycloneDxVexParser` |
| CSAF VEX | 2.0 | `CsafVexParser` |
## 3. Canonical Representation
### 3.1 NormalizedVexStatement
```csharp
public sealed record NormalizedVexStatement
{
/// <summary>Unique statement identifier (deterministic hash).</summary>
public required string StatementId { get; init; }
/// <summary>CVE or vulnerability identifier.</summary>
public required string VulnerabilityId { get; init; }
/// <summary>Normalized status (not_affected, affected, fixed, under_investigation).</summary>
public required VexStatus Status { get; init; }
/// <summary>Justification code (when status = not_affected).</summary>
public VexJustification? Justification { get; init; }
/// <summary>Human-readable impact statement.</summary>
public string? ImpactStatement { get; init; }
/// <summary>Action statement for remediation.</summary>
public string? ActionStatement { get; init; }
/// <summary>Products affected by this statement.</summary>
public required ImmutableArray<ProductIdentifier> Products { get; init; }
/// <summary>Source document metadata.</summary>
public required VexSourceMetadata Source { get; init; }
/// <summary>Statement timestamp (UTC, ISO-8601).</summary>
public required DateTimeOffset Timestamp { get; init; }
/// <summary>Issuer information.</summary>
public required IssuerInfo Issuer { get; init; }
}
```
### 3.2 VexStatus Enum
```csharp
public enum VexStatus
{
/// <summary>Product is not affected by the vulnerability.</summary>
NotAffected = 0,
/// <summary>Product is affected and vulnerable.</summary>
Affected = 1,
/// <summary>Product was affected but is now fixed.</summary>
Fixed = 2,
/// <summary>Impact is being investigated.</summary>
UnderInvestigation = 3
}
```
### 3.3 VexJustification Enum
```csharp
public enum VexJustification
{
/// <summary>Component is not present.</summary>
ComponentNotPresent = 0,
/// <summary>Vulnerable code is not present.</summary>
VulnerableCodeNotPresent = 1,
/// <summary>Vulnerable code is not in execute path.</summary>
VulnerableCodeNotInExecutePath = 2,
/// <summary>Vulnerable code cannot be controlled by adversary.</summary>
VulnerableCodeCannotBeControlledByAdversary = 3,
/// <summary>Inline mitigations exist.</summary>
InlineMitigationsAlreadyExist = 4
}
```
## 4. Normalization Rules
### 4.1 Status Mapping
| Source Format | Source Value | Normalized Status |
|---------------|--------------|-------------------|
| OpenVEX | `not_affected` | NotAffected |
| OpenVEX | `affected` | Affected |
| OpenVEX | `fixed` | Fixed |
| OpenVEX | `under_investigation` | UnderInvestigation |
| CycloneDX | `notAffected` | NotAffected |
| CycloneDX | `affected` | Affected |
| CycloneDX | `resolved` | Fixed |
| CycloneDX | `inTriage` | UnderInvestigation |
| CSAF | `not_affected` | NotAffected |
| CSAF | `known_affected` | Affected |
| CSAF | `fixed` | Fixed |
| CSAF | `under_investigation` | UnderInvestigation |
### 4.2 Justification Mapping
| Source Format | Source Value | Normalized Justification |
|---------------|--------------|--------------------------|
| OpenVEX | `component_not_present` | ComponentNotPresent |
| OpenVEX | `vulnerable_code_not_present` | VulnerableCodeNotPresent |
| OpenVEX | `vulnerable_code_not_in_execute_path` | VulnerableCodeNotInExecutePath |
| OpenVEX | `vulnerable_code_cannot_be_controlled_by_adversary` | VulnerableCodeCannotBeControlledByAdversary |
| OpenVEX | `inline_mitigations_already_exist` | InlineMitigationsAlreadyExist |
| CycloneDX | Same as OpenVEX (camelCase) | Same mapping |
| CSAF | `component_not_present` | ComponentNotPresent |
| CSAF | `vulnerable_code_not_present` | VulnerableCodeNotPresent |
| CSAF | `vulnerable_code_not_in_execute_path` | VulnerableCodeNotInExecutePath |
| CSAF | `vulnerable_code_cannot_be_controlled_by_adversary` | VulnerableCodeCannotBeControlledByAdversary |
| CSAF | `inline_mitigations_already_exist` | InlineMitigationsAlreadyExist |
### 4.3 Product Identifier Normalization
Products are normalized to PURL (Package URL) format:
```
pkg:{ecosystem}/{namespace}/{name}@{version}?{qualifiers}#{subpath}
```
| Source | Extraction Method |
|--------|-------------------|
| OpenVEX | Direct from `product.id` if PURL, else construct from `product.identifiers` |
| CycloneDX | From `bom-ref` PURL or construct from `component.purl` |
| CSAF | From `product_id``product_identification_helper.purl` |
### 4.4 Statement ID Generation
Statement IDs are deterministic SHA-256 hashes:
```csharp
public static string GenerateStatementId(
string vulnerabilityId,
VexStatus status,
IEnumerable<string> productPurls,
string issuerId,
DateTimeOffset timestamp)
{
var input = $"{vulnerabilityId}|{status}|{string.Join(",", productPurls.OrderBy(p => p))}|{issuerId}|{timestamp:O}";
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return $"stmt:{Convert.ToHexString(hash).ToLowerInvariant()[..32]}";
}
```
## 5. Issuer Directory Integration
Normalized statements include issuer information from the Issuer Directory:
```csharp
public sealed record IssuerInfo
{
/// <summary>Issuer identifier (e.g., "vendor:redhat", "vendor:canonical").</summary>
public required string IssuerId { get; init; }
/// <summary>Display name.</summary>
public required string DisplayName { get; init; }
/// <summary>Trust tier (authoritative, trusted, community, unknown).</summary>
public required IssuerTrustTier TrustTier { get; init; }
/// <summary>Issuer's signing key fingerprints (if signed).</summary>
public ImmutableArray<string> SigningKeyFingerprints { get; init; }
}
public enum IssuerTrustTier
{
Authoritative = 0, // Vendor/maintainer of the product
Trusted = 1, // Known security research org
Community = 2, // Community contributor
Unknown = 3 // Unverified source
}
```
## 6. API Governance
### 6.1 Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/vex/statements` | GET | Query normalized statements |
| `/api/v1/vex/statements/{id}` | GET | Get specific statement |
| `/api/v1/vex/normalize` | POST | Normalize a VEX document |
| `/api/v1/vex/issuers` | GET | List known issuers |
| `/api/v1/vex/issuers/{id}` | GET | Get issuer details |
### 6.2 Query Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `vulnerability` | string | Filter by CVE/vulnerability ID |
| `product` | string | Filter by PURL (URL-encoded) |
| `status` | enum | Filter by VEX status |
| `issuer` | string | Filter by issuer ID |
| `since` | datetime | Statements after timestamp |
| `limit` | int | Max results (default: 100, max: 1000) |
| `cursor` | string | Pagination cursor |
### 6.3 Response Format
```json
{
"statements": [
{
"statementId": "stmt:a1b2c3d4e5f6...",
"vulnerabilityId": "CVE-2024-1234",
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"products": ["pkg:npm/lodash@4.17.21"],
"issuer": {
"issuerId": "vendor:lodash",
"displayName": "Lodash Maintainers",
"trustTier": "authoritative"
},
"timestamp": "2024-12-19T10:30:00Z"
}
],
"cursor": "next_page_token",
"total": 42
}
```
## 7. Precedence Rules
When multiple statements exist for the same vulnerability+product:
1. **Timestamp:** Later statements supersede earlier ones
2. **Trust Tier:** Higher trust tiers take precedence (Authoritative > Trusted > Community > Unknown)
3. **Specificity:** More specific product matches win (exact version > version range > package)
## 8. Validation
All normalized statements must pass:
1. `vulnerabilityId` matches CVE/GHSA/vendor pattern
2. `status` is a valid enum value
3. `products` contains at least one valid PURL
4. `timestamp` is valid ISO-8601 UTC
5. `issuer.issuerId` exists in Issuer Directory or is marked Unknown
---
## Changelog
| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | 2025-12-19 | Initial release |

View File

@@ -0,0 +1,149 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stellaops.dev/schemas/excititor/vex_overlay.schema.json",
"title": "Excititor VEX Overlay",
"description": "Graph-ready overlay built from Link-Not-Merge observations and linksets. Immutable and append-only; ordered for deterministic pagination and caching.",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"generatedAt",
"tenant",
"purl",
"advisoryId",
"source",
"status",
"observations",
"provenance"
],
"properties": {
"schemaVersion": {
"type": "string",
"enum": ["1.0.0"]
},
"generatedAt": {
"type": "string",
"format": "date-time"
},
"tenant": {
"type": "string",
"description": "Tenant identifier used for storage partitioning."
},
"purl": {
"type": "string",
"description": "Normalized package URL for the component."
},
"advisoryId": {
"type": "string",
"description": "Upstream advisory identifier (e.g., GHSA, RHSA, CVE)."
},
"source": {
"type": "string",
"description": "Linkset source identifier (matches Concelier linkset source)."
},
"status": {
"type": "string",
"enum": [
"affected",
"not_affected",
"under_investigation",
"fixed",
"unknown"
]
},
"justifications": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["kind", "reason"],
"properties": {
"kind": {
"type": "string",
"description": "Reason code aligned to VEX statement taxonomy."
},
"reason": {
"type": "string",
"description": "Human-readable justification text."
},
"evidence": {
"type": "array",
"items": {
"type": "string",
"description": "Observation or linkset id contributing to this justification."
}
},
"weight": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Optional confidence weight."
}
}
}
},
"conflicts": {
"type": "array",
"description": "Conflicts detected in linkset normalization.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["field", "reason"],
"properties": {
"field": { "type": "string" },
"reason": { "type": "string" },
"values": {
"type": "array",
"items": { "type": "string" }
},
"sourceIds": {
"type": "array",
"items": { "type": "string" }
}
}
}
},
"observations": {
"type": "array",
"description": "Ordered list of Link-Not-Merge observation references feeding this overlay.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "contentHash", "fetchedAt"],
"properties": {
"id": { "type": "string" },
"contentHash": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
"fetchedAt": { "type": "string", "format": "date-time" }
}
},
"minItems": 1
},
"provenance": {
"type": "object",
"additionalProperties": false,
"required": ["linksetId", "linksetHash", "observationHashes"],
"properties": {
"linksetId": { "type": "string" },
"linksetHash": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
"observationHashes": {
"type": "array",
"items": { "type": "string", "pattern": "^sha256:[A-Fa-f0-9]{64}$" },
"minItems": 1
},
"policyHash": { "type": "string" },
"sbomContextHash": { "type": "string" },
"planCacheKey": { "type": "string" },
"generatedBy": { "type": "string" }
}
},
"cache": {
"type": "object",
"additionalProperties": false,
"properties": {
"cached": { "type": "boolean" },
"cachedAt": { "type": "string", "format": "date-time" },
"ttlSeconds": { "type": "integer", "minimum": 0 }
}
}
}
}

View File

@@ -0,0 +1,37 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://stellaops.dev/schemas/excititor/vex_raw.schema.json",
"title": "Excititor VEX Raw Document",
"$comment": "Note (2025-12): The gridFsObjectId field is legacy. Since Sprint 4400, all large content is stored in PostgreSQL with RustFS. This field exists only for backward compatibility with migrated data.",
"type": "object",
"additionalProperties": true,
"required": ["_id", "providerId", "format", "sourceUri", "retrievedAt", "digest"],
"properties": {
"_id": {
"type": "string",
"description": "Content-addressed digest; equals `digest`."
},
"providerId": { "type": "string", "minLength": 1 },
"format": { "type": "string", "enum": ["csaf", "cyclonedx", "openvex"] },
"sourceUri": { "type": "string", "minLength": 1 },
"retrievedAt": { "type": "string", "format": "date-time" },
"digest": { "type": "string", "minLength": 32 },
"content": {
"oneOf": [
{ "type": "string", "contentEncoding": "base64" },
{ "type": "string" }
],
"description": "Inline payload if below size threshold; may be empty when stored in RustFS (legacy: GridFS prior to Sprint 4400)."
},
"gridFsObjectId": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
},
"metadata": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
}