11 KiB
Explainability Schema
Last updated: 2025-12-13. Owner: Policy Guild + Docs Guild.
This document defines the explainability schema addressing gaps EX1-EX10 from the November 2025 product findings. It specifies the canonical format for vulnerability verdict explanations, DSSE signing policy, CAS storage rules, and export/replay formats.
1. Overview
Explainability provides auditable, machine-readable rationale for every vulnerability verdict. Each explanation includes:
- Decision chain: Ordered list of rules/policies that contributed to the verdict
- Evidence links: References to graphs, runtime facts, VEX statements, and SBOM components
- Confidence scores: Per-rule and aggregate confidence values
- Redaction metadata: PII handling and data classification
2. Gap Resolutions
EX1: Schema/Canonicalization + Hashes
Explanation schema:
{
"schema": "stellaops.explanation@v1",
"explanation_id": "explain:sha256:{hex}",
"finding_id": "P-7:S-42:pkg:maven/log4j@2.14.1:CVE-2021-44228",
"verdict": {
"status": "affected",
"severity": {"normalized": "Critical", "score": 10.0},
"confidence": 0.92
},
"decision_chain": [
{
"rule_id": "rule:reachability_gate",
"rule_version": "1.0.0",
"inputs": {
"reachability.state": "CR",
"reachability.confidence": 0.92
},
"output": {"allowed": true, "contribution": 0.4},
"evidence_refs": ["cas://reachability/graphs/blake3:..."]
},
{
"rule_id": "rule:severity_baseline",
"rule_version": "1.0.0",
"inputs": {
"cvss_base": 10.0,
"epss_percentile": 0.95
},
"output": {"severity": "Critical", "contribution": 0.6},
"evidence_refs": ["cas://advisories/CVE-2021-44228.json"]
}
],
"aggregate_confidence": 0.88,
"created_at": "2025-12-13T10:00:00Z",
"policy_version": "sha256:...",
"graph_revision_id": "rev:blake3:..."
}
Canonicalization rules:
- JSON keys sorted alphabetically at all levels
- Arrays in
decision_chainordered by rule execution sequence evidence_refsarrays sorted alphabetically- No whitespace, UTF-8 encoding
- Hash computed over canonical JSON:
sha256(canonical_json)
EX2: DSSE Predicate/Signing Policy
DSSE predicate type:
stella.ops/explanation@v1
Signing policy:
| Element | Required | Signer |
|---|---|---|
| Explanation body | Yes | Policy Engine key |
| Graph DSSE reference | Yes (if reachability cited) | Scanner key |
| VEX DSSE reference | Yes (if VEX cited) | Policy Engine key |
DSSE envelope structure:
{
"payloadType": "application/vnd.stellaops.explanation+json",
"payload": "<base64(canonical_explanation_json)>",
"signatures": [
{
"keyid": "policy-engine-signing-2025",
"sig": "base64:..."
}
]
}
Signing requirements:
- All explanations must be signed before CAS storage
- Signing key must be registered in Authority key store
- Key rotation triggers re-signing of active explanations (configurable)
EX3: CAS Storage Rules for Evidence
Storage layout:
cas://explanations/
{sha256}/ # Explanation body
{sha256}.dsse # DSSE envelope
by-finding/{finding_id}/ # Index by finding
by-policy/{policy_digest}/ # Index by policy version
by-graph/{graph_revision_id}/ # Index by graph revision
Storage rules:
- Explanations are immutable after signing
- New verdicts create new explanation documents (no updates)
- Previous explanations are retained per retention policy
- Cross-references validated at write time (graphs, VEX must exist)
Deduplication:
- Identical canonical JSON produces identical hash
- CAS returns existing reference if content matches
EX4: Link to Decision/Policy and graph_revision_id
Required links:
{
"links": {
"policy_version": "sha256:7e1d...",
"policy_uri": "cas://policy/versions/sha256:7e1d...",
"graph_revision_id": "rev:blake3:a1b2...",
"graph_uri": "cas://reachability/revisions/blake3:a1b2...",
"sbom_digest": "sha256:def4...",
"sbom_uri": "cas://scanner-artifacts/sbom.cdx.json",
"vex_digest": "sha256:e5f6...",
"vex_uri": "cas://excititor/vex/openvex.json"
}
}
Validation:
- All linked artifacts must exist at explanation creation time
- Links are verified during replay/audit
- Broken links cause replay verification failure
EX5: Export/Replay Bundle Format
Export bundle manifest:
{
"schema": "stellaops.explanation.bundle@v1",
"bundle_id": "bundle:explain:2025-12-13",
"created_at": "2025-12-13T10:00:00Z",
"explanations": [
{
"explanation_id": "explain:sha256:...",
"finding_id": "...",
"explanation_uri": "explanations/sha256:....json",
"dsse_uri": "explanations/sha256:....dsse"
}
],
"dependencies": {
"graphs": [
{"revision_id": "rev:blake3:...", "uri": "graphs/blake3:....json"}
],
"policies": [
{"digest": "sha256:...", "uri": "policies/sha256:....json"}
],
"vex_statements": [
{"digest": "sha256:...", "uri": "vex/sha256:....json"}
]
},
"verification": {
"bundle_hash": "sha256:...",
"signature": "base64:...",
"signed_by": "policy-engine-signing-2025"
}
}
Replay verification:
stella explain verify --bundle ./explanation-bundle.tgz
# Output:
Bundle: bundle:explain:2025-12-13
Explanations: 42
Dependencies: 5 graphs, 2 policies, 12 VEX
Verifying explanations...
Canonical hashes: 42/42 MATCH
DSSE signatures: 42/42 VALID
Dependency links: 42/42 RESOLVED
Replay verification PASSED.
EX6: PII/Redaction Rules
Redaction categories:
| Category | Redaction | Example |
|---|---|---|
| User identifiers | Hash | user:alice -> user:sha256:a1b2... |
| IP addresses | Mask | 192.168.1.100 -> 192.168.x.x |
| File paths | Normalize | /home/alice/code/... -> {HOME}/code/... |
| Email addresses | Hash | alice@example.com -> email:sha256:... |
| API keys/tokens | Omit | Authorization: Bearer xxx -> [REDACTED] |
Redaction metadata:
{
"redaction": {
"applied": true,
"level": "standard",
"fields_redacted": ["actor.email", "evidence.file_path"],
"redaction_policy": "stellaops.redaction.standard@v1"
}
}
Export modes:
--redacted(default): Apply standard redaction--full: Include all data (requiresexplain:export:fullscope)--audit: Include redaction audit trail
EX7: Size Budgets
Limits:
| Element | Default Limit | Configurable |
|---|---|---|
| Explanation body | 256 KB | Yes |
| Decision chain entries | 100 | Yes |
| Evidence refs per rule | 20 | Yes |
| Total evidence refs | 200 | Yes |
| Path entries | 50 | No |
Truncation behavior:
When limits are exceeded:
- Log warning with truncation details
- Add
truncationmetadata to explanation - Store full evidence in separate CAS object
- Include
full_evidence_urireference
{
"truncation": {
"applied": true,
"elements_truncated": ["decision_chain", "evidence_refs"],
"full_evidence_uri": "cas://explanations/full/sha256:..."
}
}
EX8: Versioning
Schema versioning:
- Schema version in
schemafield:stellaops.explanation@v1 - Breaking changes increment major version
- Minor changes (additive fields) use v1.x
- Backward compatibility maintained for 2 major versions
Migration support:
stella explain migrate --from v1 --to v2 --input ./explanations/
# Output:
Migrating 1000 explanations from v1 to v2...
Migrated: 998
Skipped (already v2): 2
Migration complete.
Version compatibility matrix:
| API Version | Schema v1 | Schema v2 |
|---|---|---|
| 1.0.x | Full | N/A |
| 1.1.x | Full | Full |
| 2.0.x | Read-only | Full |
EX9: Golden Fixtures/Tests
Test fixture location:
tests/Explanation/
fixtures/
simple-affected.json
simple-not-affected.json
with-reachability-evidence.json
multi-rule-chain.json
truncated-evidence.json
redacted-pii.json
golden/
simple-affected.golden.json
simple-affected.golden.dsse
datasets/explanations/
schema/
explanation.schema.json
samples/
log4j-affected/
explanation.json
expected-hash.txt
Test categories:
- Canonicalization tests: Verify hash stability across JSON reordering
- DSSE signing tests: Verify signature creation and verification
- Redaction tests: Verify PII handling
- Truncation tests: Verify size budget enforcement
- Replay tests: Verify bundle export/import cycle
- Migration tests: Verify version upgrade paths
CI integration:
# .gitea/workflows/explanation-tests.yml
explanation-tests:
runs-on: ubuntu-latest
steps:
- name: Run explanation tests
run: dotnet test src/Policy/__Tests/StellaOps.Policy.Explanation.Tests
- name: Verify golden fixtures
run: scripts/verify-golden-fixtures.sh tests/Explanation/golden/
EX10: Determinism Guarantees
Determinism requirements:
- Same inputs produce identical
explanation_idhash - Decision chain ordering is stable (execution order)
- Evidence refs sorted alphabetically
- Timestamps use UTC ISO-8601 with millisecond precision
- Floating-point values rounded to 6 decimal places
Verification:
# Run twice with same inputs, verify identical hashes
stella explain generate --finding "..." --output a.json
stella explain generate --finding "..." --output b.json
diff a.json b.json # Should be empty
# Or use built-in verify
stella explain verify-determinism --finding "..." --iterations 3
3. API Reference
3.1 Generate Explanation
POST /api/policy/findings/{findingId}/explain
Authorization: Bearer <token>
Content-Type: application/json
{
"mode": "full",
"include_evidence": true,
"redaction_level": "standard"
}
3.2 Get Explanation
GET /api/explanations/{explanationId}
Authorization: Bearer <token>
Accept: application/json
3.3 Export Explanation Bundle
POST /api/explanations/export
Authorization: Bearer <token>
Content-Type: application/json
{
"finding_ids": ["...", "..."],
"include_dependencies": true,
"redaction_level": "standard"
}
3.4 Verify Explanation
POST /api/explanations/{explanationId}/verify
Authorization: Bearer <token>
4. CLI Reference
# Generate explanation for a finding
stella explain generate --finding "P-7:S-42:pkg:maven/log4j@2.14.1:CVE-2021-44228"
# Export explanation bundle
stella explain export --findings ./finding-ids.txt --output ./bundle.tgz
# Verify explanation
stella explain verify --explanation ./explanation.json --dsse ./explanation.dsse
# Verify bundle
stella explain verify --bundle ./bundle.tgz
# Check determinism
stella explain verify-determinism --finding "..." --iterations 5
5. Related Documentation
- Function-Level Evidence - Evidence chain guide
- Graph Revision Schema - Graph versioning
- Policy API - Policy Engine REST API
- DSSE Predicates - Signing specifications
Last updated: 2025-12-13. See Sprint 0401 EXPLAIN-GAPS-401-064 for change history.