---
title: The Generation Fact Log
slug: concepts/generation-fact-log
public: true
category: concepts
template: concept
order: 6
description: Every committed write also appends a self-verifying "fact" — an after-image commit record — to an append-only log. What facts are, the crash-safety model, the scanFacts() streaming surface, family stamps, and how index providers consume the log for sequential heals.
next:
  - concepts/consistency-model
  - guides/snapshots-and-time-travel
---

# The Generation Fact Log

Since 8.4.0, every committed generation also appends a **fact** — a compact record of what each
touched entity or relationship *became* — to an append-only, checksummed log under
`_generations/facts/`. Where the generational history answers *"what did things look like
before?"* (before-images, powering `asOf()` and rollback), the fact log answers *"what happened,
in order?"* — one sequential, self-verifying stream of the store's present being written.

Nothing about querying changes. The fact log exists for three consumers:

1. **Index heals and rebuilds** — one sequential read in commit order replaces a per-entity
   directory walk over millions of files.
2. **Incremental catch-up** — a derived index that knows which generation it reflects reads *just
   the gap*, instead of rebuilding from scratch.
3. **Replay and audit tooling** — anything that wants the store's committed timeline as a stream.

## What a fact is

One fact per committed generation:

- **`generation`** and **`timestamp`** — which commit, when.
- **`ops`** — every write in that commit: `{ kind: 'noun' | 'verb', id, record }` where `record`
  holds the entity's full after-image (both stored legs), or **`null` for a tombstone** — a
  removal carries no body, by design.
- **`meta`** — the transaction metadata `transact()` was submitted with, when present.
- **`blobHashes`** — content-blob references, for exact reclamation accounting.

Facts accumulate **from the first write after upgrading** — pre-existing history is not
retroactively converted, and consumers fall back to the enumeration walk when no log exists.

## Crash safety, in one paragraph

Facts are appended and fsynced **inside the same durability window as the commit itself**, before
the commit point — so after a crash, the log can only ever be *ahead* of committed truth, never
behind it with a hole. On open, the store reconciles the log back to the committed watermark:
torn tails are detected by per-record checksums and cut; whole records beyond the watermark are
truncated. The invariant every reader can rely on: **an absent generation was never committed; a
present fact was.** `transact()` facts are durable the moment `transact()` returns; single-op
facts share the same group-commit flush as the rest of their generation, so a hard kill loses the
fact and the generation *together* — never a torn state.

## Reading the log

```typescript
const scan = brain.scanFacts({ fromGeneration: 1 })
if (scan) {
  // Telemetry up front — progress bars get a denominator from second zero.
  console.log(scan.headGeneration, scan.segmentCount, scan.approxFactCount)

  for await (const batch of scan.batches()) {
    // Each batch: { facts, firstGeneration, lastGeneration, factCount, byteSize, segmentId }
    for (const fact of batch.facts) {
      for (const op of fact.ops) {
        if (op.record === null) {
          // a tombstone: op.id was removed in this generation
        }
      }
    }
  }

  console.log(scan.summary()) // { factsYielded, segmentsRead } — the cross-check
}
```

- `scanFacts()` returns `null` when the store hosts no fact log (older store, or a storage adapter
  without binary append support) — fall back to enumerating entities.
- Scans run against a **snapshot**: facts appended after the scan opens never bleed in, each fact
  is yielded exactly once, and a detected gap aborts loudly — never a silent skip.
- `brain.factSegmentPaths()` returns the immutable, *sealed* segment files for zero-copy consumers
  (the append-mutable tail is excluded — read it through `scanFacts()`).

## Family stamps: how a projection proves it's current

Anything derived from the store — an index, the entity file tree itself — carries a **family
stamp**: a small JSON record of *which committed generation the projection reflects*
(`sourceGeneration`) plus the invariants that verify it whole (exact per-file byte sizes for
bounded families; rollup invariants like entity counts for unbounded ones). At open, coherence is
a **comparison**, not a walk:

- stamp equals the committed watermark and invariants hold → serve;
- stamp is behind → the projection reads just the gap from the fact log;
- invariants fail → loud, named divergence — `brain.repairIndex()` rebuilds from canonical and
  re-stamps.

The verifier is exported (`verifyFamilyStamp`) so every projection — TypeScript or native — runs
literally the same check.

## For plugin authors: the storage capability

Index providers receive the storage adapter, not the brain — so the host wires the log onto it.
Feature-detect and prefer the stream; fall back to enumeration:

```typescript
const scan = storage.scanFacts?.({ fromGeneration: stamp.sourceGeneration + 1 })
if (scan) {
  // sequential catch-up from the log
} else {
  // enumeration walk (older store or adapter)
}
const committed = storage.committedGeneration?.() // the watermark stamps compare against
```

Providers must never construct their own reader over the log's files — the open path belongs to
the single writer (it reconciles the log at open); the capability is the sanctioned seam.
