Skip to content

Repository Structure

record-store/
├── apps/
│   ├── record-store-cli/       the record-store binary
│   └── record-store-server/    the record-store-server binary and startup wiring
├── crates/                     19 library crates
├── console/                    Next.js web console
├── deploy/docker/              Dockerfiles and Compose files
├── docs/                       this documentation
├── tests/
│   ├── compatibility/          real-SDK tests against a live server
│   └── rust-audit.sh           dependency audit with its documented exception
└── Cargo.toml                  workspace manifest

Crates

Roughly bottom-up:

Crate Responsibility
record-store-core Domain types, identifiers, validation. Depends on nothing else here
record-store-config Loading, environment overlay, secret redaction, validation
record-store-observability Tracing setup
record-store-storage Local payload storage, chunked encryption, layout
record-store-metadata Catalog: buckets, objects, versions, multipart, quotas
record-store-auth Service accounts, credentials, policies, evaluation
record-store-audit Durable append-only audit trail
record-store-events Storage events and signed webhook delivery
record-store-sharing Share and embed capabilities
record-store-lifecycle Bounded, restart-safe expiration worker
record-store-service Object and bucket services over storage and metadata
record-store-s3 S3 protocol adapter: SigV4, routing, XML
record-store-api Management API, sharing routes, metrics
record-store-erasure Not wired in. Nothing depends on it

cargo metadata lists further crates that are not part of the documented deployment and are not covered here.

Data flow

flowchart TB
    C[Client] --> S3[record-store-s3]
    C2[Console / CLI] --> API[record-store-api]
    S3 --> SVC[record-store-service]
    API --> SVC
    SVC --> META[record-store-metadata]
    SVC --> STORE[record-store-storage]

Metadata and storage sit behind interfaces the service layer depends on, so a change to either backend does not reach the protocol crates above it.

Where to make a change

Change Crate
A new S3 operation record-store-s3, plus record-store-service if it needs new behaviour
A new management route record-store-api
A new configuration setting record-store-config — sections, partial, environment, validate
A new CLI command apps/record-store-cli
Policy actions or evaluation record-store-auth
Console UI console/

Adding a configuration setting touches four files in record-store-config: the section struct and its default, the partial struct used for TOML, the environment overlay, and validation. Missing one produces a setting that silently does nothing.

Tests live beside the code

Unit tests are #[cfg(test)] mod tests in the same file as what they test. Integration tests that need a running binary are under apps/*/tests/.

Test fixtures shared within a crate go in a test_support module.

See Testing.

Console

console/
├── app/          Next.js routes
├── components/   shared UI
├── features/     per-area code: access, audit, buckets, events, integrity,
│                 objects, overview, sharing, system, webhooks
├── hooks/
├── lib/
├── e2e/          Playwright
└── test/

The console is a client of the management API, never a second source of truth. It holds no state the server does not, and it is a separate image so a headless deployment carries no frontend.

Deployment files

File Purpose
Dockerfile The server image
Dockerfile.console The console image
compose.yml The server alone, development
compose.console.yml The server plus console, development
compose.ghcr.yml The server plus console from the published images, on a host you control
docker-compose.ghcr.yaml Coolify, from the published images
docker-compose.yaml Coolify, built from source

The two docker-compose.* files are the Coolify ones: they use expose rather than ports and declare Coolify magic variables for the domains and secrets. The compose.* files are for a plain docker compose up and are not deployable on Coolify. See Coolify.

Workspace settings

[workspace.lints.rust]
unsafe_code = "forbid"

[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"
unimplemented = "deny"

forbid cannot be overridden in a crate. Anything that would need unsafe needs a different approach.

Denying todo! and unimplemented! means a partial implementation cannot be merged behind a placeholder — either it works, or the code path does not exist.