34. Reference ledger engine, concurrency, partitioning, and retention
A conforming ledger can be implemented with different storage products, but the reference profile uses PostgreSQL because unique constraints, transactional row locks, serializable transactions and an atomic outbox can enforce the write invariants directly.…
A conforming ledger can be implemented with different storage products, but the reference profile uses PostgreSQL because unique constraints, transactional row locks, serializable transactions and an atomic outbox can enforce the write invariants directly. The broker, graph, search, feed and telemetry stores remain disposable projections.
#34.1 Ordering model and cursors
| Order | Guarantee | Consumer contract |
|---|---|---|
| streamSequence | Gap-free uint64 inside one tenant+stream | Optimistic concurrency and aggregate replay |
| shardPosition | Gap-free uint64 inside one logical shard epoch | Canonical event delivery, projection checkpoint, and receipt chain |
| recordedAt | Store UTC instant; may tie and is not an ordering key | Bitemporal filtering and operations |
| recordId | Time-ordered UUIDv7 for locality only | Opaque identity; never authoritative ordering |
| checkpoint vector | Map logical shard epoch -> inclusive commitSequence | Consistent multi-shard projection watermark |
| public cursor | Authenticated-encrypted token or random server handle bound to query, audience, policy and checkpoint vector | Stable pagination; expires but never silently changes scope |
There is no universal global sequence
Cross-shard order is a partial order expressed through recorded causality, transaction/saga relations and a checkpoint vector. Fabricating one global counter creates a hot lock and a false temporal claim.#34.2 Reference relational schema
PostgreSQL reference DDL - compact v1.4 skeleton
CREATE TABLE stream_head (
tenant_id uuid, stream_id uuid, logical_shard_id text,
last_sequence bigint CHECK(last_sequence>=0), last_record_digest bytea,
writer_epoch bigint CHECK(writer_epoch>=0),
PRIMARY KEY(tenant_id,stream_id));
CREATE TABLE shard_head (
ledger_id uuid, tenant_id uuid, logical_shard_id text, chain_epoch bigint,
last_commit_sequence bigint CHECK(last_commit_sequence>=0),
last_receipt_digest bytea NOT NULL, publisher_epoch bigint,
shard_map_version text,
PRIMARY KEY(ledger_id,tenant_id,logical_shard_id,chain_epoch));
CREATE TABLE record_ledger (
ledger_id uuid, tenant_id uuid, logical_shard_id text, chain_epoch bigint,
commit_sequence bigint CHECK(commit_sequence>0), stream_id uuid,
stream_sequence bigint, record_id uuid, canonical_bytes bytea,
record_digest bytea,
PRIMARY KEY(ledger_id,tenant_id,logical_shard_id,chain_epoch,commit_sequence),
UNIQUE(tenant_id,record_id), UNIQUE(tenant_id,stream_id,stream_sequence));
CREATE TABLE ingest_receipt (
receipt_id uuid PRIMARY KEY, ledger_id uuid, tenant_id uuid,
logical_shard_id text, chain_epoch bigint, commit_sequence bigint,
previous_receipt_digest bytea, receipt_digest bytea, canonical_event_id uuid,
UNIQUE(ledger_id,tenant_id,logical_shard_id,chain_epoch,commit_sequence));
Outbox and projection checkpoints reuse the same logical-shard/epoch/commit key.
Appendix O defines every required column, digest and atomicity constraint.- Create 32 hash partitions initially; measure partition size and lock contention before changing. Each partition receives indexes on (stream_id, stream_sequence), (record_type, recorded_at), (recorded_at), and BRIN(valid_start_s) when history is large.
- Keep canonical_bytes and record_digest in PostgreSQL for authoritative replay. Large artifacts remain in object storage and are referenced by immutable manifests.
- Enable and force row-level security for tenant tables, but treat it as defense in depth; the application policy decision and service identity are still mandatory.
- Database roles used by services do not own tables and do not have BYPASSRLS. Migration and break-glass roles are separate, time-bound and fully audited.
#34.3 Atomic append algorithm
One-record append with fencing, idempotency and outbox
append(ctx, receivedBytes):
parsed, canonicalBytes, recordDigest = validate_and_canonicalize(receivedBytes)
requestDigest = H(domain="REQUEST", ctx.operationFamily, canonicalBytes)
retry SERIALIZABLE transaction on SQLSTATE 40001 with bounded jitter:
INSERT idempotency_binding(scoped key, requestDigest, status='STARTED')
ON CONFLICT DO NOTHING
binding = SELECT idempotency_binding FOR UPDATE by scoped key
if binding.request_digest != requestDigest: fail EQUIVOCATION
if binding.status != 'STARTED' or binding.receipt_id is not null:
return original receipt or in-progress status
head = SELECT stream_head FOR UPDATE by tenantId, streamId
require ctx.writerEpoch == head.writer_epoch
require ctx.expectedStreamSequence == head.last_sequence
nextSeq = head.last_sequence + 1
shard = stable_shard(ctx.tenantId, ctx.streamId)
nextPosition = allocate_shard_position(shard) # gaps allowed on rollback
authorize exact recordDigest and current context; fail closed on error
INSERT record_ledger, subject/relation indexes, ingest_receipt, outbox_event
UPDATE idempotency_binding SET status='COMMITTED', receipt_id=receiptId
UPDATE stream_head SET last_sequence=nextSeq, last_record_id=recordId,
last_receipt_digest=receiptDigest WHERE writer_epoch=ctx.writerEpoch
COMMIT
publisher reads committed outbox rows, emits at least once, and marks published.
Consumer deduplicates by eventId and advances checkpoint with its state atomically.PostgreSQL sequences can produce gaps on rollback; shardPosition therefore orders committed rows but is not required to be gap-free. streamSequence is allocated by the locked stream_head row and is gap-free for accepted records. A writer-epoch mismatch is terminal for that writer, even if it believes its lease remains valid.
#34.4 Batch append and multi-stream work
| Operation | Atomicity boundary | Failure behavior |
|---|---|---|
| single-stream batch | One transaction; expected starting sequence plus ordered RecordCores | All commit or none; first invalid record rejects batch |
| independent bulk import | One transaction per record or bounded microbatch | Per-item receipt; no fabricated batch atomicity |
| multi-stream semantic transaction | Allowed only when all heads live in one database transaction and lock order is deterministic | Serialize/retry whole transaction; bounded stream count |
| cross-service/cross-store workflow | Durable saga with step receipts | Complete, compensate or escalate; never claim distributed rollback |
| external provider effect | Action transaction plus provider idempotency/reconciliation | UNKNOWN_EFFECT until proven; never blind retry |
#34.5 Integrity chain and checkpointing
Hash-chain and Merkle checkpoint profile
receiptDigest[n] = SHA256(
"RTRACER\0INGEST-RECEIPT\0v1\0" ||
canonical(receipt_without_digest_and_signature)
)
receipt[n].previousReceiptDigest = receiptDigest[n-1] within one ledgerId + tenantId + logicalShardId + chainEpoch. ShardHead stores the only chain tail; commitSequence is gap-free and transactional.
Every 65,536 committed receipts, emit LedgerCheckpoint {
shardId, firstCommitSequence, lastCommitSequence, merkleRoot, terminalReceiptDigest,
recordCount, createdAt, signerKeyEpoch, backupSnapshotRefs[]
}
Daily IntegrityScrub recomputes sampled leaves, complete recent windows and
all checkpoint chains; mismatch quarantines the shard from new projection release.#34.6 Retention, deletion, and legal erasure
| Data class | Default treatment | Deletion semantics |
|---|---|---|
| portable factual RecordCore | Retain according to declared lifecycle/legal class | If erasure is required, destroy protected payload/key material and append ErasureReceipt; never pretend the fact was never processed |
| PII-bearing identifier/evidence | Field or artifact encrypted with scoped data-encryption key | Cryptographic erasure plus object deletion, replicas/backup expiry and verification receipt |
| derived public projection | Rebuildable, short retention | Delete immediately on source/policy change and invalidate cache/search/feed |
| security/audit metadata | Minimized, separately authorized, immutable for defined period | Retain only non-content fields needed for accountability; expire under policy |
| raw telemetry/media | Tiered storage with explicit purpose and expiry | Delete object bytes and segment keys; retain non-identifying manifest tombstone if permitted |
| legal hold | Scoped to named records/artifacts, authority and review date | Suspends only affected disposal; not a blanket tenant freeze |
Append-only does not mean retain every secret forever
The immutable property applies to the audit of decisions and relationships. Privacy-sensitive bytes can be separately encrypted, minimized, redacted and destroyed under an authorized disposal record. A tombstone must not preserve the deleted value in an index, log message, cache key or error string.