CAS (Content Addressable Storage) Infrastructure Contract

Status: APPROVED (infrastructure compose) — see “Implementation status” below for which parts are wired in src/ Version: 1.0.0 Last Updated: 2025-12-06 Owner: Platform Storage Guild

Overview

This contract defines the Content-Addressable Storage (CAS) infrastructure for StellaOps, using RustFS as the S3-compatible storage backend. The design provides:

Audience: Platform/storage operators deploying CAS via Docker Compose, and module engineers (Scanner, Signals, Evidence Locker, Attestor) wiring object storage into their services. The “Implementation status” section maps each claim to its compose file or src/ source so you can verify it.

Implementation status

The deployment topology in this document is realized by the devops/compose/docker-compose.cas.yaml compose file and the devops/compose/env/cas.env.example environment template (both present in the repo). The three RustFS instances, the cas-lifecycle manager, the per-service account/bucket bindings, and the retention values below are all verifiable against that compose file.

On the application side, services reach CAS through two distinct access models, depending on the consuming module:

  1. S3-compatible (Evidence Locker). The Evidence Locker object-store abstraction (StellaOps.EvidenceLocker.Core.Configuration.ObjectStoreKind) exposes two concrete backends — FileSystem and AmazonS3 (see EvidenceLockerOptions.cs). RustFS is consumed through the AmazonS3 (S3-compatible) driver: S3EvidenceObjectStore constructs an IAmazonS3 client (Amazon.S3). WORM/Object-Lock behaviour is controlled by ObjectStoreOptions.EnforceWriteOnce and AmazonS3StoreOptions.ObjectLock (ObjectLockMode.Governance / ObjectLockMode.Compliance).

  2. Native RustFS HTTP API (Scanner, Signals). Other consuming services talk to RustFS directly over its HTTP object API rather than the S3 API. Scanner’s RustFsArtifactObjectStore (StellaOps.Scanner.Storage.ObjectStore) and Signals’ RustFsRuntimeFactsArtifactStore / RustFsCallgraphArtifactStore issue PUT/GET/DELETE requests against buckets/{bucket}/objects/{key} and set RustFS-specific headers (X-RustFS-Immutable, X-RustFS-Retain-Seconds). Scanner storage selects the backend via a Driver setting (ScannerStorageDefaults.ObjectStoreProviders: s3, minio, or rustfs) plus a RustFsOptions block (BaseUrl, ApiKey, ApiKeyHeader).

In short, RustFS is both an S3-compatible target (for Evidence Locker) and a first-class, RustFS-named client target (for Scanner/Signals) within src/. The access model in this document describes the storage-tier configuration; application services reach the buckets via the per-service account keys defined here, supplied either as S3 credentials or as the RustFS API-key header.

Architecture

                          CAS Infrastructure
  ┌───────────────────┐  ┌───────────────────┐  ┌─────────────────────┐
  │     rustfs-cas    │  │  rustfs-evidence  │  │  rustfs-attestation │
  │     (mutable)     │  │    (immutable)    │  │     (immutable)     │
  ├───────────────────┤  ├───────────────────┤  ├─────────────────────┤
  │ • scanner-        │  │ • evidence-       │  │ • attestations      │
  │   artifacts       │  │   bundles         │  │ • dsse-envelopes    │
  │ • surface-cache   │  │ • merkle-roots    │  │ • rekor-receipts    │
  │ • runtime-facts   │  │ • hash-chains     │  │                     │
  │ • signals-data    │  │                   │  │                     │
  │ • provenance-feed │  │                   │  │                     │
  │ • replay-bundles  │  │                   │  │                     │
  └─────────┬─────────┘  └─────────┬─────────┘  └──────────┬──────────┘
            │                      │                       │
            └──────────────────────┼───────────────────────┘
                                   │
                       ┌───────────┴───────────┐
                       │     cas-lifecycle     │
                       │  (retention manager)  │
                       └───────────────────────┘

Retention Policies

Aligned with best-in-class on-premise vulnerability scanners:

Data TypeRetentionRationaleScanner Comparison
Vulnerability DB7 daysFresh advisories requiredTrivy: 7d, Grype: 5d
SBOM artifacts365 daysAudit compliance (SOC2, ISO27001)Anchore: 365d
Scan results90 daysCommon compliance windowSnyk: 90d enterprise
Evidence bundlesIndefiniteImmutable audit trailN/A (StellaOps unique)
AttestationsIndefiniteSigned, verifiableN/A (StellaOps unique)
Temp artifacts1 dayWork-in-progress cleanupStandard practice

Access Control Matrix

Service Accounts

ServiceBucketsPermissionsPurpose
scannerscanner-artifacts, surface-cache, runtime-factsread, writeScan job artifacts, cache
signalsruntime-facts, signals-data, provenance-feedread, writeRuntime signal ingestion
replayreplay-bundles, inputs-lockread, writeDeterministic replay
ledgerevidence-bundles, merkle-roots, hash-chainsread, writeEvidence ledger writes
exporterevidence-bundlesreadExport center reads
attestorattestations, dsse-envelopes, rekor-receiptsread, writeAttestation storage
verifierattestations, dsse-envelopes, rekor-receiptsreadVerification reads
readonly*readGlobal audit access

Bucket Classification

BucketStorage TypeLifecycleAccess Pattern
scanner-artifactsrustfs-cas90 daysWrite-heavy
surface-cacherustfs-cas7 daysRead-heavy, cache
runtime-factsrustfs-cas90 daysWrite-heavy
signals-datarustfs-cas90 daysWrite-heavy
provenance-feedrustfs-cas90 daysAppend-only
replay-bundlesrustfs-cas365 daysRead-heavy
inputs-lockrustfs-cas365 daysWrite-once
evidence-bundlesrustfs-evidenceIndefiniteWrite-once
merkle-rootsrustfs-evidenceIndefiniteAppend-only
hash-chainsrustfs-evidenceIndefiniteAppend-only
attestationsrustfs-attestationIndefiniteWrite-once
dsse-envelopesrustfs-attestationIndefiniteWrite-once
rekor-receiptsrustfs-attestationIndefiniteWrite-once

Docker Compose Integration

The compose file lives at devops/compose/docker-compose.cas.yaml. Its own header documents the two supported invocations:

# Use with the main stack (per the compose file header)
docker compose -f docker-compose.stella-ops.yml -f docker-compose.cas.yaml up -d

# Standalone CAS
docker compose -f docker-compose.cas.yaml up -d

The docker-compose.cas.yaml header pairs CAS with docker-compose.stella-ops.yml. (There is a docker-compose.dev.yml override in devops/compose/, but no docker-compose.dev.yaml.)

Environment Variables

See devops/compose/env/cas.env.example for full configuration.

Key variables:

Endpoints

Each RustFS container listens on 0.0.0.0:8080 internally (serve --listen 0.0.0.0:8080 --root /data) and is published on the host ports below. The host ports are configurable via RUSTFS_CAS_PORT / RUSTFS_EVIDENCE_PORT / RUSTFS_ATTESTATION_PORT (defaults shown).

ServiceHost PortContainer PortPurpose
rustfs-cas81808080Mutable CAS storage (lifecycle-managed)
rustfs-evidence81818080Immutable evidence (--immutable)
rustfs-attestation81828080Immutable attestations (--immutable)

The RustFS image (registry.stella-ops.org/stellaops/rustfs:2025.09.2) is external, but the HTTP object path layout it serves is exercised from this repo. The RustFsMigrator tool (src/Tools/RustFsMigrator/) writes to /api/v1/buckets/{bucket}/objects/{key} (RustFsMigratorPaths.cs), and the Scanner native client (RustFsArtifactObjectStore) addresses buckets/{bucket}/objects/{key} relative to a configurable RustFsOptions.BaseUrl. The /health endpoint used by the compose healthchecks is also verifiable here (see Health Checks). Evidence Locker consumes the same buckets through an S3-compatible (AmazonS3) client instead.

Health Checks

All RustFS instances expose /health endpoint:

curl http://localhost:8180/health  # CAS
curl http://localhost:8181/health  # Evidence
curl http://localhost:8182/health  # Attestations

Migration from MinIO

PARTIALLY IMPLEMENTED. The stella cas migrate and stella cas verify CLI commands referenced in older drafts do not exist — there is no cas command group in src/Cli/StellaOps.Cli/Commands/ (CommandFactory.cs registers no cas verb). However, a dedicated migration utility does ship: RustFsMigrator (src/Tools/RustFsMigrator/). It reads objects from an S3/MinIO source bucket (ListObjectsV2 via Amazon.S3) and PUTs them into RustFS at /api/v1/buckets/{bucket}/objects/{key}, with --dry-run support and retry handling. So MinIO → RustFS migration is an S3-source read plus a RustFS-native write performed by this tool (not a plain S3-to-S3 copy, and not a stella subcommand), followed by re-pointing each service’s storage credentials/endpoint.

For existing deployments using MinIO:

  1. Deploy CAS infrastructure alongside MinIO
  2. Configure scanner/signals services with the RustFS endpoint and the per-service account keys (RUSTFS_*_KEY) from cas.env.example (Scanner sets Driver=rustfs plus the RustFsOptions block; Evidence Locker uses the AmazonS3 driver against the RustFS S3 endpoint)
  3. Migrate data with the RustFsMigrator tool (src/Tools/RustFsMigrator/), which copies objects from the MinIO/S3 source buckets into the matching RustFS buckets (no stella cas migrate CLI verb — see note above)
  4. Verify object integrity by comparing SHA-256 content addresses (no stella cas verify command ships today — see note above)
  5. Update service configurations to use the RustFS endpoint
  6. Decommission MinIO after validation

Tasks Unblocked

This contract unblocks the CAS approval gate (PREP-SIGNALS-24-002):

Changelog

DateVersionChange
2026-05-301.0.1Doc/code reconciliation: corrected the claim that no RustFS-named client exists in src/ (Scanner RustFsArtifactObjectStore, Signals RustFsRuntimeFacts/Callgraph stores); documented the native RustFS HTTP path layout (buckets/{bucket}/objects/{key}, /api/v1/... in RustFsMigrator); documented the shipped RustFsMigrator tool in the MinIO migration section.
2025-12-061.0.0Initial contract with RustFS, retention policies, access controls