feat: Add VEX Status Chip component and integration tests for reachability drift detection
- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips. - Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling. - Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation. - Updated project references to include the new Reachability Drift library.
This commit is contained in:
346
docs/modules/signals/contracts/signals-provenance-contract.md
Normal file
346
docs/modules/signals/contracts/signals-provenance-contract.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# Signals Provenance Contract v1.0.0
|
||||
|
||||
**Status:** APPROVED
|
||||
**Version:** 1.0.0
|
||||
**Effective:** 2025-12-19
|
||||
**Owner:** Signals Guild + Platform Storage Guild
|
||||
**Sprint:** SPRINT_0140_0001_0001 (unblocks SIGNALS-24-002, 24-003, 24-004, 24-005)
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This contract defines the provenance tracking for runtime facts, callgraph storage, and CAS (Content-Addressable Storage) promotion policies. It enables deterministic, auditable signal processing with signed manifests and attestations.
|
||||
|
||||
## 2. Schema References
|
||||
|
||||
| Schema | Location |
|
||||
|--------|----------|
|
||||
| Provenance Feed | `docs/schemas/provenance-feed.schema.json` |
|
||||
| Runtime Facts | `docs/signals/runtime-facts.md` |
|
||||
| Reachability Input | `docs/modules/policy/contracts/reachability-input-contract.md` |
|
||||
|
||||
## 3. CAS Storage Architecture
|
||||
|
||||
### 3.1 Bucket Structure
|
||||
|
||||
```
|
||||
cas://signals/
|
||||
├── callgraphs/
|
||||
│ ├── {tenant}/
|
||||
│ │ ├── {graph_id}.ndjson.zst # Compressed callgraph
|
||||
│ │ └── {graph_id}.meta.json # Callgraph metadata
|
||||
│ └── global/
|
||||
│ └── ...
|
||||
├── manifests/
|
||||
│ ├── {graph_id}.json # Signed manifest
|
||||
│ └── {graph_id}.json.dsse # DSSE envelope
|
||||
├── runtime-facts/
|
||||
│ ├── {tenant}/
|
||||
│ │ ├── {batch_id}.ndjson.zst # Runtime fact batch
|
||||
│ │ └── {batch_id}.provenance.json # Provenance record
|
||||
│ └── global/
|
||||
│ └── ...
|
||||
└── attestations/
|
||||
└── {batch_id}.dsse # Batch attestation
|
||||
```
|
||||
|
||||
### 3.2 Access Policies
|
||||
|
||||
| Principal | callgraphs | manifests | runtime-facts | attestations |
|
||||
|-----------|------------|-----------|---------------|--------------|
|
||||
| Signals Service | read/write | read/write | read/write | read/write |
|
||||
| Policy Engine | read | read | read | read |
|
||||
| Scanner Worker | write | - | - | - |
|
||||
| Audit Service | read | read | read | read |
|
||||
| All Others | deny | deny | deny | deny |
|
||||
|
||||
### 3.3 Retention Policies
|
||||
|
||||
| Content Type | Retention | GC Policy |
|
||||
|--------------|-----------|-----------|
|
||||
| Manifests | Indefinite | Never delete |
|
||||
| Callgraphs (referenced) | Indefinite | Never delete |
|
||||
| Callgraphs (orphan) | 30 days | Rolling GC |
|
||||
| Runtime Facts | 90 days | Rolling GC |
|
||||
| Attestations | Indefinite | Never delete |
|
||||
|
||||
## 4. Manifest Schema
|
||||
|
||||
### 4.1 CallgraphManifest
|
||||
|
||||
```csharp
|
||||
public sealed record CallgraphManifest
|
||||
{
|
||||
/// <summary>Unique graph identifier (ULID).</summary>
|
||||
public required string GraphId { get; init; }
|
||||
|
||||
/// <summary>SHA-256 digest of callgraph content.</summary>
|
||||
public required string Digest { get; init; }
|
||||
|
||||
/// <summary>Programming language.</summary>
|
||||
public required string Language { get; init; }
|
||||
|
||||
/// <summary>Source identifier (scanner, analyzer, runtime agent).</summary>
|
||||
public required string Source { get; init; }
|
||||
|
||||
/// <summary>When the callgraph was created.</summary>
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
/// <summary>Tenant scope.</summary>
|
||||
public required string TenantId { get; init; }
|
||||
|
||||
/// <summary>Component PURL.</summary>
|
||||
public required string ComponentPurl { get; init; }
|
||||
|
||||
/// <summary>Entry points discovered.</summary>
|
||||
public ImmutableArray<string> EntryPoints { get; init; }
|
||||
|
||||
/// <summary>Node count in the graph.</summary>
|
||||
public int NodeCount { get; init; }
|
||||
|
||||
/// <summary>Edge count in the graph.</summary>
|
||||
public int EdgeCount { get; init; }
|
||||
|
||||
/// <summary>Signing key ID.</summary>
|
||||
public string? SignerKeyId { get; init; }
|
||||
|
||||
/// <summary>Signature (Base64).</summary>
|
||||
public string? Signature { get; init; }
|
||||
|
||||
/// <summary>Rekor log UUID if transparency-logged.</summary>
|
||||
public string? RekorUuid { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 JSON Example
|
||||
|
||||
```json
|
||||
{
|
||||
"graphId": "01HWXYZ123456789ABCDEFGHJK",
|
||||
"digest": "sha256:7d9cd5f1a2a0dd9a41a2c43a5b7d8a0bcd9e34cf39b3f43a70595c834f0a4aee",
|
||||
"language": "javascript",
|
||||
"source": "stella-callgraph-node",
|
||||
"createdAt": "2025-12-19T10:00:00Z",
|
||||
"tenantId": "tenant-001",
|
||||
"componentPurl": "pkg:npm/%40acme/backend@1.2.3",
|
||||
"entryPoints": ["src/index.js", "src/server.js"],
|
||||
"nodeCount": 1523,
|
||||
"edgeCount": 4892,
|
||||
"signerKeyId": "signals-signer-2025-001",
|
||||
"signature": "base64...",
|
||||
"rekorUuid": "24296fb24b8ad77a..."
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Runtime Facts Provenance
|
||||
|
||||
### 5.1 ProvenanceRecord
|
||||
|
||||
```csharp
|
||||
public sealed record RuntimeFactProvenance
|
||||
{
|
||||
/// <summary>Provenance record ID (ULID).</summary>
|
||||
public required string ProvenanceId { get; init; }
|
||||
|
||||
/// <summary>Callgraph ID this fact batch relates to.</summary>
|
||||
public required string CallgraphId { get; init; }
|
||||
|
||||
/// <summary>Batch ID for this fact set.</summary>
|
||||
public required string BatchId { get; init; }
|
||||
|
||||
/// <summary>When facts were ingested.</summary>
|
||||
public required DateTimeOffset IngestedAt { get; init; }
|
||||
|
||||
/// <summary>When facts were received from source.</summary>
|
||||
public required DateTimeOffset ReceivedAt { get; init; }
|
||||
|
||||
/// <summary>Tenant scope.</summary>
|
||||
public required string TenantId { get; init; }
|
||||
|
||||
/// <summary>Source host/service.</summary>
|
||||
public required string Source { get; init; }
|
||||
|
||||
/// <summary>Pipeline version (git SHA or build ID).</summary>
|
||||
public required string PipelineVersion { get; init; }
|
||||
|
||||
/// <summary>SHA-256 of raw fact blob.</summary>
|
||||
public required string ProvenanceHash { get; init; }
|
||||
|
||||
/// <summary>Signing key ID.</summary>
|
||||
public string? SignerKeyId { get; init; }
|
||||
|
||||
/// <summary>Rekor UUID or skip reason.</summary>
|
||||
public string? RekorUuid { get; init; }
|
||||
|
||||
/// <summary>Skip reason if not transparency-logged.</summary>
|
||||
public string? SkipReason { get; init; }
|
||||
|
||||
/// <summary>Fact count in this batch.</summary>
|
||||
public int FactCount { get; init; }
|
||||
|
||||
/// <summary>Fact types included.</summary>
|
||||
public ImmutableArray<string> FactTypes { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Enrichment Pipeline
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ Runtime Agent │────▶│ Signals Ingest │────▶│ CAS Storage │
|
||||
│ (runtime-facts) │ │ (provenance) │ │ (facts+prov) │
|
||||
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ DSSE Attestation │
|
||||
│ (per batch) │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
## 6. API Endpoints
|
||||
|
||||
### 6.1 Callgraph Management
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `POST /signals/callgraphs` | POST | Store new callgraph |
|
||||
| `GET /signals/callgraphs/{graphId}` | GET | Retrieve callgraph |
|
||||
| `GET /signals/callgraphs/{graphId}/manifest` | GET | Get signed manifest |
|
||||
| `GET /signals/callgraphs/by-purl/{purl}` | GET | Find by component PURL |
|
||||
|
||||
### 6.2 Runtime Facts
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `POST /signals/runtime-facts` | POST | Ingest runtime fact batch |
|
||||
| `GET /signals/runtime-facts/{batchId}` | GET | Retrieve fact batch |
|
||||
| `GET /signals/runtime-facts/{batchId}/provenance` | GET | Get provenance record |
|
||||
| `GET /signals/runtime-facts/ndjson` | GET | Stream facts (with provenance) |
|
||||
|
||||
### 6.3 Query Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `tenant` | string | Filter by tenant |
|
||||
| `callgraph_id` | string | Filter by callgraph |
|
||||
| `since` | datetime | Facts after timestamp |
|
||||
| `include_provenance` | bool | Include provenance_hash and callgraph_id |
|
||||
|
||||
## 7. Signing and Attestation
|
||||
|
||||
### 7.1 Manifest Signing
|
||||
|
||||
All callgraph manifests are signed using:
|
||||
- Algorithm: `ECDSA-P256-SHA256` or `Ed25519`
|
||||
- Key management: Via Authority service key registry
|
||||
- Transparency: Optional Sigstore Rekor logging
|
||||
|
||||
```csharp
|
||||
public interface IManifestSigner
|
||||
{
|
||||
Task<SignedManifest> SignAsync(
|
||||
CallgraphManifest manifest,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> VerifyAsync(
|
||||
SignedManifest signedManifest,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Batch Attestation
|
||||
|
||||
Runtime fact batches are attested using in-toto/DSSE:
|
||||
|
||||
```csharp
|
||||
public sealed record RuntimeFactAttestation
|
||||
{
|
||||
public required string PredicateType { get; init; } // "https://stella.ops/attestation/runtime-facts/v1"
|
||||
public required string BatchId { get; init; }
|
||||
public required string ProvenanceHash { get; init; }
|
||||
public required int FactCount { get; init; }
|
||||
public required DateTimeOffset Timestamp { get; init; }
|
||||
public required ImmutableArray<string> Subjects { get; init; } // callgraph IDs
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Telemetry
|
||||
|
||||
### 8.1 Metrics
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| `signals_callgraphs_stored_total` | counter | `language`, `tenant` | Callgraphs stored |
|
||||
| `signals_callgraph_nodes_total` | histogram | `language` | Nodes per callgraph |
|
||||
| `signals_runtime_facts_ingested_total` | counter | `fact_type`, `tenant` | Facts ingested |
|
||||
| `signals_runtime_facts_batch_size` | histogram | - | Facts per batch |
|
||||
| `signals_provenance_records_total` | counter | - | Provenance records created |
|
||||
| `signals_attestations_created_total` | counter | - | DSSE attestations created |
|
||||
| `signals_cas_operations_total` | counter | `operation`, `result` | CAS operations |
|
||||
|
||||
### 8.2 Alerts
|
||||
|
||||
```yaml
|
||||
groups:
|
||||
- name: signals-provenance
|
||||
rules:
|
||||
- alert: SignalsAttestationFailure
|
||||
expr: increase(signals_attestations_created_total{result="failure"}[5m]) > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Runtime fact attestation failures detected"
|
||||
|
||||
- alert: SignalsProvenanceMissing
|
||||
expr: signals_runtime_facts_ingested_total - signals_provenance_records_total > 100
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Runtime facts missing provenance records"
|
||||
```
|
||||
|
||||
## 9. Configuration
|
||||
|
||||
```yaml
|
||||
# etc/signals.yaml
|
||||
Signals:
|
||||
CAS:
|
||||
BucketPrefix: "cas://signals"
|
||||
WriteEnabled: true
|
||||
RetentionDays:
|
||||
RuntimeFacts: 90
|
||||
OrphanCallgraphs: 30
|
||||
|
||||
Provenance:
|
||||
Enabled: true
|
||||
SignManifests: true
|
||||
AttestBatches: true
|
||||
RekorEnabled: true # Set to false for air-gap
|
||||
|
||||
Signing:
|
||||
KeyId: "signals-signer-2025-001"
|
||||
Algorithm: "ECDSA-P256-SHA256"
|
||||
```
|
||||
|
||||
## 10. Validation Rules
|
||||
|
||||
1. `GraphId` must be valid ULID
|
||||
2. `Digest` must be valid `sha256:` prefixed hex
|
||||
3. `Language` must be known language identifier
|
||||
4. `TenantId` must exist in Authority tenant registry
|
||||
5. `ComponentPurl` must be valid Package URL
|
||||
6. `ProvenanceHash` must match recomputed hash of fact blob
|
||||
7. Manifests must have valid signature if `SignManifests: true`
|
||||
8. Attestations must have valid DSSE envelope
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0.0 | 2025-12-19 | Initial release - unblocks SIGNALS-24-002 through 24-005 |
|
||||
Reference in New Issue
Block a user