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

SchemaLocation
Provenance Feeddocs/modules/signals/schemas/provenance-feed.schema.json
Runtime Factsdocs/modules/signals/guides/runtime-facts.md
Reachability Inputdocs/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

Principalcallgraphsmanifestsruntime-factsattestations
Signals Serviceread/writeread/writeread/writeread/write
Policy Enginereadreadreadread
Scanner Workerwrite---
Audit Servicereadreadreadread
All Othersdenydenydenydeny

3.3 Retention Policies

Content TypeRetentionGC Policy
ManifestsIndefiniteNever delete
Callgraphs (referenced)IndefiniteNever delete
Callgraphs (orphan)30 daysRolling GC
Runtime Facts90 daysRolling GC
AttestationsIndefiniteNever delete

4. Manifest Schema

4.1 CallgraphManifest

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

{
  "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

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>BLAKE3-256 digest 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)      │
                        └──────────────────┘

5.3 Fail-Closed Batch Storage

Runtime fact batches must not be represented as CAS evidence unless the raw payload was persisted by a configured runtime-facts artifact store and hashed by the configured Stella Ops crypto hash provider with BLAKE3-256. Filesystem deployments store local artifacts; RustFS deployments store objects under runtime-facts/{first2}/{blake3}/runtime-facts.ndjson[.gz] by default. Missing storage, missing hash provider, empty batches, partial RustFS configuration, and unsupported storage drivers fail closed. Signals does not synthesize cas:// URIs or timestamp-derived hashes for runtime-facts batch evidence.

6. API Endpoints

6.1 Callgraph Management

EndpointMethodDescription
POST /signals/callgraphsPOSTStore new callgraph
GET /signals/callgraphs/{graphId}GETRetrieve callgraph
GET /signals/callgraphs/{graphId}/manifestGETGet signed manifest
GET /signals/callgraphs/by-purl/{purl}GETFind by component PURL

6.2 Runtime Facts

EndpointMethodDescription
POST /signals/runtime-factsPOSTIngest runtime fact batch
GET /signals/runtime-facts/{batchId}GETRetrieve fact batch
GET /signals/runtime-facts/{batchId}/provenanceGETGet provenance record
GET /signals/runtime-facts/ndjsonGETStream facts (with provenance)

6.3 Query Parameters

ParameterTypeDescription
tenantstringFilter by tenant
callgraph_idstringFilter by callgraph
sincedatetimeFacts after timestamp
include_provenanceboolInclude provenance_hash and callgraph_id

7. Signing and Attestation

7.1 Manifest Signing

All callgraph manifests are signed using:

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:

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

MetricTypeLabelsDescription
signals_callgraphs_stored_totalcounterlanguage, tenantCallgraphs stored
signals_callgraph_nodes_totalhistogramlanguageNodes per callgraph
signals_runtime_facts_ingested_totalcounterfact_type, tenantFacts ingested
signals_runtime_facts_batch_sizehistogram-Facts per batch
signals_provenance_records_totalcounter-Provenance records created
signals_attestations_created_totalcounter-DSSE attestations created
signals_cas_operations_totalcounteroperation, resultCAS operations

8.2 Alerts

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

# 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

VersionDateChanges
1.0.02025-12-19Initial release - unblocks SIGNALS-24-002 through 24-005