- Introduced `ReachabilityState`, `RuntimeHit`, `ExploitabilitySignal`, `ReachabilitySignal`, `SignalEnvelope`, `SignalType`, `TrustSignal`, and `UnknownSymbolSignal` records to define various signal types and their properties. - Implemented JSON serialization attributes for proper data interchange. - Created project files for the new signal contracts library and corresponding test projects. - Added deterministic test fixtures for micro-interaction testing. - Included cryptographic keys for secure operations with cosign.
609 lines
19 KiB
JSON
609 lines
19 KiB
JSON
{
|
|
"$id": "https://stella.ops/schema/ledger-airgap-staleness.json",
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"title": "LedgerAirgapStaleness",
|
|
"description": "LEDGER-AIRGAP-56-002 staleness specification for air-gap provenance tracking and freshness enforcement",
|
|
"type": "object",
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/StalenessConfig" },
|
|
{ "$ref": "#/$defs/BundleProvenance" },
|
|
{ "$ref": "#/$defs/StalenessMetrics" },
|
|
{ "$ref": "#/$defs/StalenessValidationResult" }
|
|
],
|
|
"$defs": {
|
|
"StalenessConfig": {
|
|
"type": "object",
|
|
"required": ["configType", "freshnessThresholdSeconds"],
|
|
"description": "Configuration for air-gap staleness enforcement policies",
|
|
"properties": {
|
|
"configType": {
|
|
"type": "string",
|
|
"const": "STALENESS_CONFIG"
|
|
},
|
|
"freshnessThresholdSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"default": 604800,
|
|
"description": "Maximum age in seconds before data is considered stale (default: 7 days = 604800)"
|
|
},
|
|
"enforcementMode": {
|
|
"type": "string",
|
|
"enum": ["STRICT", "WARN", "DISABLED"],
|
|
"default": "STRICT",
|
|
"description": "How staleness violations are handled"
|
|
},
|
|
"gracePeriodSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"default": 86400,
|
|
"description": "Grace period after threshold before hard enforcement (default: 1 day)"
|
|
},
|
|
"allowedDomains": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Domains exempt from staleness enforcement",
|
|
"examples": [["vex-advisories", "vulnerability-feeds"]]
|
|
},
|
|
"notificationThresholds": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/$defs/NotificationThreshold"
|
|
},
|
|
"description": "Alert thresholds for approaching staleness"
|
|
}
|
|
}
|
|
},
|
|
"NotificationThreshold": {
|
|
"type": "object",
|
|
"required": ["percentOfThreshold", "severity"],
|
|
"properties": {
|
|
"percentOfThreshold": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 100,
|
|
"description": "Percentage of freshness threshold to trigger notification"
|
|
},
|
|
"severity": {
|
|
"type": "string",
|
|
"enum": ["info", "warning", "critical"],
|
|
"description": "Notification severity level"
|
|
},
|
|
"channels": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"enum": ["email", "slack", "teams", "webhook", "metric"]
|
|
},
|
|
"description": "Notification delivery channels"
|
|
}
|
|
}
|
|
},
|
|
"BundleProvenance": {
|
|
"type": "object",
|
|
"required": ["provenanceType", "bundleId", "importedAt", "sourceTimestamp"],
|
|
"description": "Provenance record for an imported air-gap bundle",
|
|
"properties": {
|
|
"provenanceType": {
|
|
"type": "string",
|
|
"const": "BUNDLE_PROVENANCE"
|
|
},
|
|
"bundleId": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"description": "Unique bundle identifier"
|
|
},
|
|
"domainId": {
|
|
"type": "string",
|
|
"description": "Bundle domain (vex-advisories, vulnerability-feeds, etc.)"
|
|
},
|
|
"importedAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When bundle was imported into this environment"
|
|
},
|
|
"sourceTimestamp": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Original generation timestamp from source environment"
|
|
},
|
|
"sourceEnvironment": {
|
|
"type": "string",
|
|
"description": "Source environment identifier",
|
|
"examples": ["prod-us-east", "staging", "upstream-mirror"]
|
|
},
|
|
"bundleDigest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
"description": "SHA-256 digest of the bundle contents"
|
|
},
|
|
"manifestDigest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
"description": "SHA-256 digest of the bundle manifest"
|
|
},
|
|
"stalenessSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Calculated staleness at import time (importedAt - sourceTimestamp)"
|
|
},
|
|
"timeAnchor": {
|
|
"$ref": "#/$defs/TimeAnchor",
|
|
"description": "Time anchor used for staleness calculation"
|
|
},
|
|
"attestation": {
|
|
"$ref": "#/$defs/BundleAttestation",
|
|
"description": "Attestation covering this bundle"
|
|
},
|
|
"exports": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/$defs/ExportRecord"
|
|
},
|
|
"description": "Exports included in this bundle"
|
|
},
|
|
"metadata": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
},
|
|
"description": "Additional bundle metadata"
|
|
}
|
|
}
|
|
},
|
|
"TimeAnchor": {
|
|
"type": "object",
|
|
"required": ["anchorType", "timestamp"],
|
|
"description": "Trusted time reference for staleness calculations",
|
|
"properties": {
|
|
"anchorType": {
|
|
"type": "string",
|
|
"enum": ["NTP", "ROUGHTIME", "HARDWARE_CLOCK", "ATTESTATION_TSA", "MANUAL"],
|
|
"description": "Type of time anchor"
|
|
},
|
|
"timestamp": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Anchor timestamp (UTC)"
|
|
},
|
|
"source": {
|
|
"type": "string",
|
|
"description": "Time source identifier",
|
|
"examples": ["pool.ntp.org", "roughtime.cloudflare.com", "tsa.stellaops.local"]
|
|
},
|
|
"uncertainty": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Time uncertainty in milliseconds"
|
|
},
|
|
"signatureDigest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
"description": "Digest of time attestation signature if applicable"
|
|
},
|
|
"verified": {
|
|
"type": "boolean",
|
|
"description": "Whether time anchor was cryptographically verified"
|
|
}
|
|
}
|
|
},
|
|
"BundleAttestation": {
|
|
"type": "object",
|
|
"properties": {
|
|
"predicateType": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "in-toto predicate type",
|
|
"examples": ["https://stella.ops/attestation/airgap-bundle/v1"]
|
|
},
|
|
"envelopeDigest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
"description": "DSSE envelope digest"
|
|
},
|
|
"signedAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When attestation was signed"
|
|
},
|
|
"keyId": {
|
|
"type": "string",
|
|
"description": "Signing key identifier"
|
|
},
|
|
"transparencyLog": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "Rekor transparency log entry if available"
|
|
}
|
|
}
|
|
},
|
|
"ExportRecord": {
|
|
"type": "object",
|
|
"required": ["exportId", "key", "format", "createdAt", "artifactDigest"],
|
|
"properties": {
|
|
"exportId": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"description": "Export identifier"
|
|
},
|
|
"key": {
|
|
"type": "string",
|
|
"description": "Export key",
|
|
"examples": ["vex-openvex-all", "vuln-critical-cve"]
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["openvex", "csaf", "cyclonedx", "spdx", "ndjson", "json"],
|
|
"description": "Export data format"
|
|
},
|
|
"createdAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When export was created"
|
|
},
|
|
"artifactDigest": {
|
|
"type": "string",
|
|
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
"description": "Export artifact digest"
|
|
},
|
|
"recordCount": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Number of records in export"
|
|
}
|
|
}
|
|
},
|
|
"StalenessMetrics": {
|
|
"type": "object",
|
|
"required": ["metricsType", "collectedAt"],
|
|
"description": "Staleness metrics for monitoring and dashboards",
|
|
"properties": {
|
|
"metricsType": {
|
|
"type": "string",
|
|
"const": "STALENESS_METRICS"
|
|
},
|
|
"collectedAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When metrics were collected"
|
|
},
|
|
"tenantId": {
|
|
"type": "string",
|
|
"description": "Tenant scope"
|
|
},
|
|
"domainMetrics": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/$defs/DomainStalenessMetric"
|
|
},
|
|
"description": "Per-domain staleness metrics"
|
|
},
|
|
"aggregates": {
|
|
"$ref": "#/$defs/AggregateMetrics",
|
|
"description": "Aggregate staleness statistics"
|
|
}
|
|
}
|
|
},
|
|
"DomainStalenessMetric": {
|
|
"type": "object",
|
|
"required": ["domainId", "stalenessSeconds", "lastImportAt"],
|
|
"properties": {
|
|
"domainId": {
|
|
"type": "string",
|
|
"description": "Domain identifier"
|
|
},
|
|
"stalenessSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Current staleness in seconds"
|
|
},
|
|
"lastImportAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Last bundle import timestamp"
|
|
},
|
|
"lastSourceTimestamp": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Source timestamp of last import"
|
|
},
|
|
"bundleCount": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Total bundles imported for this domain"
|
|
},
|
|
"isStale": {
|
|
"type": "boolean",
|
|
"description": "Whether domain data exceeds staleness threshold"
|
|
},
|
|
"percentOfThreshold": {
|
|
"type": "number",
|
|
"minimum": 0,
|
|
"description": "Staleness as percentage of threshold"
|
|
},
|
|
"projectedStaleAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When data will become stale if no updates"
|
|
}
|
|
}
|
|
},
|
|
"AggregateMetrics": {
|
|
"type": "object",
|
|
"properties": {
|
|
"totalDomains": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Total domains tracked"
|
|
},
|
|
"staleDomains": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Domains exceeding staleness threshold"
|
|
},
|
|
"warningDomains": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Domains approaching staleness threshold"
|
|
},
|
|
"healthyDomains": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Domains within healthy staleness range"
|
|
},
|
|
"maxStalenessSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Maximum staleness across all domains"
|
|
},
|
|
"avgStalenessSeconds": {
|
|
"type": "number",
|
|
"minimum": 0,
|
|
"description": "Average staleness across all domains"
|
|
},
|
|
"oldestBundle": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Timestamp of oldest bundle source data"
|
|
}
|
|
}
|
|
},
|
|
"StalenessValidationResult": {
|
|
"type": "object",
|
|
"required": ["validationType", "validatedAt", "passed"],
|
|
"description": "Result of staleness validation check",
|
|
"properties": {
|
|
"validationType": {
|
|
"type": "string",
|
|
"const": "STALENESS_VALIDATION"
|
|
},
|
|
"validatedAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When validation was performed"
|
|
},
|
|
"passed": {
|
|
"type": "boolean",
|
|
"description": "Whether validation passed"
|
|
},
|
|
"context": {
|
|
"type": "string",
|
|
"enum": ["EXPORT", "QUERY", "POLICY_EVAL", "ATTESTATION"],
|
|
"description": "Context where validation was triggered"
|
|
},
|
|
"domainId": {
|
|
"type": "string",
|
|
"description": "Domain being validated"
|
|
},
|
|
"stalenessSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Current staleness at validation time"
|
|
},
|
|
"thresholdSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Threshold used for validation"
|
|
},
|
|
"enforcementMode": {
|
|
"type": "string",
|
|
"enum": ["STRICT", "WARN", "DISABLED"],
|
|
"description": "Enforcement mode at validation time"
|
|
},
|
|
"error": {
|
|
"$ref": "#/$defs/StalenessError",
|
|
"description": "Error details if validation failed"
|
|
},
|
|
"warnings": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/$defs/StalenessWarning"
|
|
},
|
|
"description": "Warnings generated during validation"
|
|
}
|
|
}
|
|
},
|
|
"StalenessError": {
|
|
"type": "object",
|
|
"required": ["code", "message"],
|
|
"properties": {
|
|
"code": {
|
|
"type": "string",
|
|
"enum": [
|
|
"ERR_AIRGAP_STALE",
|
|
"ERR_AIRGAP_NO_BUNDLE",
|
|
"ERR_AIRGAP_TIME_ANCHOR_MISSING",
|
|
"ERR_AIRGAP_TIME_DRIFT",
|
|
"ERR_AIRGAP_ATTESTATION_INVALID"
|
|
],
|
|
"description": "Error code"
|
|
},
|
|
"message": {
|
|
"type": "string",
|
|
"description": "Human-readable error message"
|
|
},
|
|
"domainId": {
|
|
"type": "string",
|
|
"description": "Affected domain"
|
|
},
|
|
"stalenessSeconds": {
|
|
"type": "integer",
|
|
"description": "Actual staleness when error occurred"
|
|
},
|
|
"thresholdSeconds": {
|
|
"type": "integer",
|
|
"description": "Threshold that was exceeded"
|
|
},
|
|
"recommendation": {
|
|
"type": "string",
|
|
"description": "Recommended action to resolve"
|
|
}
|
|
}
|
|
},
|
|
"StalenessWarning": {
|
|
"type": "object",
|
|
"required": ["code", "message"],
|
|
"properties": {
|
|
"code": {
|
|
"type": "string",
|
|
"enum": [
|
|
"WARN_AIRGAP_APPROACHING_STALE",
|
|
"WARN_AIRGAP_TIME_UNCERTAINTY_HIGH",
|
|
"WARN_AIRGAP_BUNDLE_OLD",
|
|
"WARN_AIRGAP_NO_RECENT_IMPORT"
|
|
],
|
|
"description": "Warning code"
|
|
},
|
|
"message": {
|
|
"type": "string",
|
|
"description": "Human-readable warning message"
|
|
},
|
|
"percentOfThreshold": {
|
|
"type": "number",
|
|
"description": "Current staleness as percentage of threshold"
|
|
},
|
|
"projectedStaleAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When data will become stale"
|
|
}
|
|
}
|
|
},
|
|
"LedgerProjectionUpdate": {
|
|
"type": "object",
|
|
"required": ["projectionId", "airgap"],
|
|
"description": "Update to ledger_projection collection for staleness tracking",
|
|
"properties": {
|
|
"projectionId": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"description": "Projection document ID"
|
|
},
|
|
"airgap": {
|
|
"type": "object",
|
|
"required": ["stalenessSeconds", "lastBundleId", "lastImportAt"],
|
|
"properties": {
|
|
"stalenessSeconds": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Current staleness in seconds"
|
|
},
|
|
"lastBundleId": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"description": "Last imported bundle ID"
|
|
},
|
|
"lastImportAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "When last bundle was imported"
|
|
},
|
|
"lastSourceTimestamp": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Source timestamp of last bundle"
|
|
},
|
|
"timeAnchorAt": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"description": "Time anchor used for calculation"
|
|
},
|
|
"isStale": {
|
|
"type": "boolean",
|
|
"description": "Whether projection data is stale"
|
|
}
|
|
},
|
|
"description": "Air-gap staleness fields for projection"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"examples": [
|
|
{
|
|
"configType": "STALENESS_CONFIG",
|
|
"freshnessThresholdSeconds": 604800,
|
|
"enforcementMode": "STRICT",
|
|
"gracePeriodSeconds": 86400,
|
|
"allowedDomains": ["local-overrides"],
|
|
"notificationThresholds": [
|
|
{
|
|
"percentOfThreshold": 75,
|
|
"severity": "warning",
|
|
"channels": ["slack", "metric"]
|
|
},
|
|
{
|
|
"percentOfThreshold": 90,
|
|
"severity": "critical",
|
|
"channels": ["email", "slack", "metric"]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"provenanceType": "BUNDLE_PROVENANCE",
|
|
"bundleId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"domainId": "vex-advisories",
|
|
"importedAt": "2025-11-21T10:00:00Z",
|
|
"sourceTimestamp": "2025-11-20T08:00:00Z",
|
|
"sourceEnvironment": "prod-us-east",
|
|
"bundleDigest": "sha256:7d9cd5f1a2a0dd9a41a2c43a5b7d8a0bcd9e34cf39b3f43a70595c834f0a4aee",
|
|
"stalenessSeconds": 93600,
|
|
"timeAnchor": {
|
|
"anchorType": "NTP",
|
|
"timestamp": "2025-11-21T10:00:00Z",
|
|
"source": "pool.ntp.org",
|
|
"uncertainty": 50,
|
|
"verified": true
|
|
},
|
|
"exports": [
|
|
{
|
|
"exportId": "660e8400-e29b-41d4-a716-446655440001",
|
|
"key": "vex-openvex-all",
|
|
"format": "openvex",
|
|
"createdAt": "2025-11-20T08:00:00Z",
|
|
"artifactDigest": "sha256:8d9cd5f1a2a0dd9a41a2c43a5b7d8a0bcd9e34cf39b3f43a70595c834f0a4aef",
|
|
"recordCount": 15420
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"validationType": "STALENESS_VALIDATION",
|
|
"validatedAt": "2025-11-28T14:30:00Z",
|
|
"passed": false,
|
|
"context": "EXPORT",
|
|
"domainId": "vex-advisories",
|
|
"stalenessSeconds": 691200,
|
|
"thresholdSeconds": 604800,
|
|
"enforcementMode": "STRICT",
|
|
"error": {
|
|
"code": "ERR_AIRGAP_STALE",
|
|
"message": "VEX advisories data is stale (8 days old, threshold 7 days)",
|
|
"domainId": "vex-advisories",
|
|
"stalenessSeconds": 691200,
|
|
"thresholdSeconds": 604800,
|
|
"recommendation": "Import a fresh VEX bundle from upstream using 'stella airgap import'"
|
|
}
|
|
}
|
|
]
|
|
}
|