{ "$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'" } } ] }