Crux Daemon · 6. The data directory

Everything the daemon knows lives under one directory. Back it up and you have backed up the daemon. This chapter is the map: what is in there, which code writes each file, what breaks if it goes missing, and which files grow forever.

This chapter is reference.

6.0 In plain English

The daemon keeps all of its state in one directory tree. Facts, shard segments, signing keys, node identity, routing state and locks all live under it. That is the whole storage model, and it has a useful practical consequence: this directory is the backup unit. Copy it and you have copied the daemon; restore it onto another machine and the daemon comes up believing exactly what it believed before.

The reason to have a chapter about a directory is that the tree is not self-describing. A file named MANIFEST or passport.key does not tell you what depends on it, whether it is regenerated automatically, or what happens if it is missing when the process next starts. This chapter answers those three questions for every file: what writes it, what breaks without it, and whether the damage is recoverable.

You will need it when you set up backups and want to know whether it is safe to exclude something; when a disk fills and you need to know which files grow without bound (§6.7); when you are moving a daemon between hosts; and in the worst case, after something has been deleted and you need to know what you have actually lost. §6.5 is ordered by blast radius for exactly that moment.

Two things surprise people. The first is that deleting facts.jsonl is a total, silent memory loss: the daemon starts clean, reports itself healthy, and will not tell you anything is missing. Nothing in the startup path treats an absent journal as an error, because an absent journal is also what a brand-new install looks like. The second is that several subsystems you would expect to find as files are not files at all. The coordination plane, extension grants, session bindings and legal holds are all stored as facts under reserved entity prefixes, so searching the tree for a coord/ directory finds nothing and tells you nothing. §6.4 lists them, and the practical upshot is that GET /v1/facts inspects far more of the daemon's state than its name suggests.

6.1 Two roots, usually the same directory

SettingEnv varConfig-file keyFallback chainDefault
data_dirCORECRUXD_DATA_DIRdaemon.data_direnv, then daemon.data_dir, then daemon.state_dir, then the literal../CoreCruxData/v1
state_dirCORECRUXD_STATE_DIRdaemon.state_direnv, then daemon.state_dir, then data_direquals data_dir

Source: config.rs:834-842.

// crates/corecruxd/src/config.rs:834
let data_dir = env_string("CORECRUXD_DATA_DIR")
    .map(|value| expand_path(&value))
    .or(file_data_dir)
    .or_else(|| file_state_dir.clone())
    .unwrap_or_else(|| PathBuf::from("../CoreCruxData/v1"));

The default is relative to the daemon's working directory (config.rs:838). Two daemons started from two different directories get two different data dirs and two different LOCK files, so the single-instance guard cannot help you. Set CORECRUXD_DATA_DIR to an absolute path.

Path values go through expand_path, which substitutes $XDG_STATE_HOME, $XDG_CONFIG_HOME, $HOME and a leading ~/ (config.rs:772-791). There is no implicit XDG default, an $XDG_STATE_HOME/crux layout only happens if you configure it.

Both roots are created at startup, in this order, before anything else touches disk: create_dir_all(state_dir) (main.rs:475), create_dir_all(data_dir) (main.rs:476), then acquire_lock(data_dir) (main.rs:477).

Two subsystems read CORECRUXD_DATA_DIR directly from the process environment rather than from Config, so they silently no-op if the variable is unset even when the YAML file sets a data dir: the activity journal (activity.rs:598) and the MCP sync tool (tools/sync.rs:50).

6.2 The tree

<data_dir>/                                   # CORECRUXD_DATA_DIR, default ../CoreCruxData/v1
├── LOCK                                      # daemon single-instance lock (fs2 exclusive)
├── CONTROL.json                              # operator valve state
├── .install-uuid                             # session-plane install identity
├── passport.key                              # daemon Ed25519 passport seed (in state_dir)
├── passport.claimed                          # anonymous-claim marker (in state_dir)
├── audit-export-signing.key                  # persistent audit-export Ed25519 key
│
├── meta/
│   ├── node.json                             # node identity (node_id, addrs, build)
│   └── routing/
│       ├── LOCK                              # shard-map publish lock
│       ├── current                           # ASCII u64 + newline: active shardmap version
│       ├── shardmap.v<00000001>.json         # immutable versioned shard maps
│       └── tmp/                              # staging for atomic publish
│
├── shards/
│   └── shard-<NNNN>/                         # zero-padded 4-digit shard id
│       ├── LOCK                              # per-shard exclusive open lock
│       ├── MANIFEST                          # CCMF append-only catalogue
│       ├── segments/
│       │   ├── seg-<seq:020>-<id:32hex>.ccxseg    # sealed segment (CCS3/CCF3)
│       │   ├── seg-<seq:020>-<id:32hex>.ccxhead   # open head segment (unsealed)
│       │   ├── seg-<seq:020>-<id:32hex>.ccxi      # BM25 companion index
│       │   └── seg-<seq:020>-<id:32hex>.ccxv      # dense-vector companion (not produced here)
│       ├── directory/
│       │   └── dirrun-l<level>-r<run:020>.ccxdir  # LSM directory run (CCDR)
│       ├── projections/
│       │   ├── projections.meta.json
│       │   ├── artifact_living_state.snapshot.ccxs
│       │   ├── artifact_relations.snapshot.ccxs
│       │   ├── artifact_dependents.snapshot.ccxs
│       │   ├── pressure_events.snapshot.ccxs
│       │   └── cold/
│       │       ├── relations/{<2hex>/<64hex>.ccxblk, segments/}
│       │       └── dependents/{<2hex>/<64hex>.ccxblk, segments/}
│       ├── receipts/verification/<tenant>/<receipt_id>.json
│       ├── tmp/                              # in-flight segment writes
│       └── quarantine/                       # swept orphans; never auto-deleted
│
├── facts.jsonl                               # fact store journal (primary memory)
├── entities.jsonl                            # substrate entity journal
├── substrate-edges.jsonl                     # substrate edge journal
├── sessions.jsonl                            # memory session journal
├── cases.jsonl                               # case store journal
├── relations.jsonl                           # relations projection journal
├── witness_proofs.jsonl                      # pending + witnessed seal-chain heads
├── credit-meter.jsonl                        # credit meter journal (flag-gated)
├── session-events.jsonl                      # session-plane sealed event log
├── sync-outbox.jsonl                         # outbound sync queue
├── sync-cursor.json                          # sync replication cursor
│
├── observations/
│   ├── <sanitised-session-id>.jsonl          # signed observation/receipt records
│   ├── __governance__::gc.jsonl              # GC receipts
│   └── __agent_session__<scope>__ledger__<passport>.jsonl
│
├── sessions/
│   ├── <session_id:32hex>.json               # session registry entries
│   └── <session_id:32hex>.json.tmp           # transient
│
├── passports/
│   └── <passport_id>.key                     # per-passport Ed25519 seed
│
├── console/
│   ├── settings.json                         # onboarding/console state
│   ├── chunks-index.json                     # console content chunk index
│   └── chunks-index.lock                     # index mutation lock
│
├── cost/reports.jsonl                        # cost lens (flag-gated)
├── activity/journal.jsonl                    # activity log (flag-gated)
├── provenance/verification-records.jsonl     # provenance verification records
│
├── integrations/
│   ├── index.json                            # installed pack index
│   ├── audit.jsonl                           # pack + extension audit log
│   ├── packs/<pack_id>/<version>/manifest.json
│   ├── grants/<passport_fpr>/<pack_id>.json
│   ├── github/{credentials.json, selected_repos.json}
│   └── openai/credentials.json
│
├── extensions/
│   ├── trusted-keys.json
│   ├── registry/index.json
│   └── <extension_id>/extension.wasm
│
└── studio/library/index.json                 # signed template library index

One feature-conditional addition: with --features dense-embed-model and CORECRUXD_DENSE_MODEL=fastembed, the ONNX model download lands directly under the data-dir root (main.rs:952).

6.3 File by file

Lifecycle reads: boot means created or opened during startup; write means created lazily on first write; GC means there is an automatic reclamation path.

Root-level control and identity

PathWhat it isFormatWritten byLifecycle
LOCKSingle-instance guard for the whole data dir. Held for the process lifetimeEmpty file; the semantics are in the advisory flockmain.rs:2156boot; never removed
CONTROL.jsonOperator valve state: pause_ingest, pause_compaction, throttle, read_only, emergency_brakeJSON (ControlV1)main.rs:479; writer control.rs:472boot, then rewritten on any valve change
meta/node.jsonNode identity: node_id, HTTP and gRPC advertise addresses, build infoJSON (NodeMetaV1)main.rs:500boot
.install-uuid32-char hex install identity for the session plane. BLAKE3-hashed before it leaves the hostPlain text, one linecrux-session/src/passport.rs:32first session-plane use
passport.key (in state_dir)The daemon's Ed25519 passport seed, the identity that signs receiptsText-encoded 32-byte seedpath config.rs:889; writer passport.rs:232boot
passport.claimed (in state_dir)Marker that the anonymous passport claim already succeeded, so it never retriesPlain textmain.rs:151, main.rs:2622on first successful claim
passports/<id>.keyPer-passport Ed25519 seed, e.g. personal-default.key, work-default.keyText-encoded 32-byte seedpassports.rs:688; seeded at main.rs:1019boot, when seeding is on
audit-export-signing.keyPersistent Ed25519 key for signing audit-export bundles. Created with owner-only permissionsBytes, mode 0600audit_signing_key.rs:119first audit export, unless the env key is set

Routing and the shard map

PathWhat it isFormatWritten byLifecycle
meta/routing/LOCKSerialises shard-map publishes. Blocking lock_exclusiveEmpty file + flockshard_map.rs:108boot; held only during a publish
meta/routing/currentThe active shard-map version numberASCII u64 plus a newlineshard_map.rs:107boot, rewritten per publish
meta/routing/shardmap.v<NNNNNNNN>.jsonImmutable versioned shard map, 8-digit zero-paddedPretty JSON (ShardMapV1)shard_map.rs:144on publish; never deleted
meta/routing/tmp/Staging for the write-then-rename publish protocol-shard_map.rs:106boot; emptied by rename

Publish protocol (shard_map.rs:182-196): write tmp/shardmap.v….json.tmp, fsync, rename into routing/, fsync the directory, write tmp/current.tmp, rename over current, fsync the directory.

The shard store

Path layout is defined once in ShardPaths::for_root (corecrux-storage/src/lib.rs:180). Shard directory names are shard-{id:04}.

Path under shards/shard-NNNN/What it isOn-disk formatWritten byLifecycle
LOCKExclusive open lock for the shard. Retries try_lock_exclusive ten times at 5 ms to absorb the deferred-fput flock release windowEmpty file + flocklib.rs:1374held while the shard is open
MANIFESTAppend-only catalogue: AddSegment, AddDirRun, RemoveDirRun, StreamMetaUpdate. The authority for which segments are live256-byte header, magic CCMF, version 1, then CRC32C-framed recordsheader manifest.rs:68; created lib.rs:1398boot; appended on every seal
segments/…​.ccxsegSealed immutable segmentHeader magic CCS3 with a 4096-byte header; footer magic CCF3, 256 bytes; TOC magic TOC1; frames magic CRX1; per-block 256-byte bloom filternaming append.rs:355; constants corecrux-segment/src/lib.rs:44on seal; immutable afterwards
segments/…​.ccxheadThe currently-appending head segment. Not tracked in MANIFESTThe same frame stream, unsealed; CCMT 64-byte commit markers delimit crash-safe boundariesnaming append.rs:518on first append; renamed to .ccxseg at seal
segments/…​.ccxiBM25 inverted-index companion built at seal time.ccxi binary, BLAKE3-hashedcompanions.rs:90on seal, only when CORECRUXD_BUILD_CCXI is on (default off)
segments/…​.ccxvDense-vector companion. Recognised by the orphan sweeper but not produced by this buildbinaryreferenced lib.rs:1468-
directory/dirrun-l<level>-r<run>.ccxdirLSM directory runMagic CCDR, 4096-byte header, 256 partitions, 12-byte entries, 32-byte extentsnaming lib.rs:565on directory compaction, default off
tmp/Staging for segment and companion writes before the atomic rename-lib.rs:189created on open; swept into quarantine/ on every open
quarantine/Where crash debris goes. Three prefixes: tmp-<ns>-<name>, orphan-<ns>-<name>, dirrun-orphan-<ns>-<name>files moved verbatimlib.rs:1439created on open; never emptied automatically
receipts/verification/<tenant>/<receipt_id>.jsonPer-receipt verification reportPretty JSONstore_v1.rs:24on verification

Durability discipline. Sealed segments and directory runs are written into tmp/, fsynced, renamed into place, then the containing directory is fsynced, and only then is the MANIFEST record appended (lib.rs:1505). A crash between the rename and the MANIFEST append leaves an orphan, which the next open quarantines, the mechanism that stops segment sequence numbers being reused.

Three-place wiring. Companion files are deliberately exempted from the orphan sweep while their .ccxseg is still MANIFEST-referenced (lib.rs:1461). Without that exemption every restart would quarantine the live retrieval indexes. The matching load-at-startup half is at main.rs:774, gated on config.build_ccxi || config.local_ingest_enabled. If you introduce a new on-disk artefact type, this is the pair of places that must both know about it.

Projections

Paths defined in ProjectionFiles (runner.rs:55).

Path under shards/shard-NNNN/projections/What it isWritten byLifecycle
projections.meta.jsonProjection cursors, schema versions, module ref list, commit id. The recovery anchor for every projection. Written via a temp file and renamemeta.rs:316boot; rewritten per commit
artifact_living_state.snapshot.ccxsLiving-state projection snapshotrunner.rs:63on commit
artifact_relations.snapshot.ccxsRelations projection snapshotrunner.rs:64on commit
pressure_events.snapshot.ccxsPressure-events projection snapshotrunner.rs:65on commit
artifact_dependents.snapshot.ccxsDependents projection snapshotrunner.rs:66on commit
cold/{relations,dependents}/<2hex>/<64hex>.ccxblkContent-addressed cold blocks; BLAKE3 hex names, sharded by first byterunner.rs:902on spill
cold/{relations,dependents}/segments/Content-addressed cold segments, 64 MiB caprunner.rs:919on spill; GC'd by gc_cold_segments_dir_v1 (runner.rs:846)

The memory-plane journals

All are append-only JSON lines, replayed at boot, rebuilt entirely in memory. There is no index file.

PathWhat it isWritten by
facts.jsonlThe fact store. Store, Delete and Supersede events. The single most important user-data file, coordination announces, punchcards, extension grants, session bindings and every store_fact call live here as facts, not as separate filesfact_store.rs:730 open and replay; fact_store.rs:764 durable append with double fsync
entities.jsonlSubstrate entity store journalentity_store.rs:99
substrate-edges.jsonlSubstrate edge store journaledge_store.rs:103
sessions.jsonlMemory session store journalsession_store.rs:89
cases.jsonlCase store journalcase_store.rs:129
relations.jsonlRelations projection journal, replayed into ProjectionStaterelations.rs:233
witness_proofs.jsonlPending and witnessed seal-chain headswitness_proofs.rs:195
credit-meter.jsonlCredit meter ledger. Only when the credit meter is enabledcredit_meter.rs:260
session-events.jsonlSession-plane sealed event log, one JSON line per sealed event, fsync per appendsealer.rs:122
sync-outbox.jsonlOutbound sync queueoutbox.rs:32
sync-cursor.jsonSync replication cursorcorecrux-memory/src/sync.rs:887
cost/reports.jsonlCost-lens reports. Only when CORECRUXD_FEATURE_COST_LENS is on, the path function returns nothing otherwise, so "feature off means zero on-disk writes"cost.rs:217
activity/journal.jsonlActivity log. Only when CORECRUXD_DATA_DIR is set in the process environment, see §6.1. Best-effort: I/O errors are swallowedactivity.rs:597
provenance/verification-records.jsonlProvenance verification recordsprovenance.rs:581
observations/<sanitised-id>.jsonlSigned observation and receipt records, one file per scoped session id. Per-record payload capped by CORECRUXD_MAX_OBSERVATION_PAYLOAD_BYTES, default 1 MiB with a 64 KiB floorobservations.rs:452

Fact-journal compaction is operator-triggered, not automatic. FactStore::compact_journal (fact_store.rs:1787) rewrites facts.jsonl into a temp file in the same directory, fsyncs it, renames atomically, then fsyncs the parent directory. Deleted facts become value-free tombstones, the original value never reaches the rewritten journal (fact_store.rs:1898). There is no scheduled compaction.

Sessions, console, integrations, extensions

PathWhat it isFormatWritten by
sessions/<session_id:32hex>.jsonSession registry entry: capability plan, TTL, canonical CBOR body hex-encodedPretty JSON, temp file and renameregistry.rs:171
console/settings.jsonConsole onboarding stateJSON, temp file and renameonboarding.rs:86
console/chunks-index.jsonConsole content chunk indexPretty JSON, temp file and renameconsole_index.rs:259
console/chunks-index.lockGuards read-modify-write of the chunk index across concurrent requestsEmpty file + flockconsole_index.rs:274
integrations/index.jsonInstalled integration-pack indexJSON, atomiccrux-integrations/src/lib.rs:1351
integrations/audit.jsonlUnified pack and extension audit log. Best-effort: an append failure is warn-logged and never fails the operationJSONLcrux-integrations/src/lib.rs:1332
integrations/packs/<id>/<version>/manifest.jsonInstalled pack manifest. Path components pass through a traversal guardJSON, atomiccrux-integrations/src/lib.rs:906
integrations/grants/<passport_fpr>/<pack_id>.jsonPer-passport pack grantJSON, atomiccrux-integrations/src/lib.rs:1344
integrations/github/credentials.jsonGitHub integration credentials, owner-only permissionsJSON, temp file and rename, mode 0600integrations_github.rs:100
integrations/github/selected_repos.jsonSelected repository listJSONintegrations_github.rs:181
integrations/openai/credentials.jsonOpenAI integration credentials, owner-only permissionsJSON, temp file and rename, mode 0600integrations_openai.rs:109
extensions/trusted-keys.jsonTrusted publisher keys for extension signature verificationJSONextension_registry.rs:76
extensions/registry/index.jsonVerified extension registry snapshot, populated by corecruxctl extensions syncJSONcorecruxctl/src/main.rs:1355
extensions/<extension_id>/extension.wasmDownloaded WASM module, SHA-256-verified before the renameWASM binary, temp file and renamewasm_dispatcher.rs:302
studio/library/index.jsonSigned template-library index, re-verified by the daemon on readJSONstudio_library.rs:68

6.4 Things that are deliberately not files

These subsystems keep their state as facts in facts.jsonl, not as their own artefacts. Looking for a file is the wrong search.

  • The coordination plane, announces, presence, punchcards and leases, under the entity prefix __coord__:: (coord.rs:105). Punchcards additionally appear in the substrate entity store (coord.rs:268).
  • Extension grants, prefix __extension_grant__:: (extension_grants.rs:12).
  • Session bindings, prefix __session_binding__:: (session_bindings.rs).
  • Legal holds, mint requests, identity links, projects and principals, all fact-backed.
  • Scheduler job health, under __sync__::<job_id> key status, readable through GET /v1/facts (main.rs:1487).

Two more things that are not daemon-owned state at all:

  • ExecPlan work items are a read-time projection over external .md files. work_execplans.rs reads $CRUX_EXECPLANS_ROOT/*.md and derives state; it writes nothing to the data dir (work_execplans.rs:1130).
  • The console SPA is served from embedded assets, not extracted to disk. Only CORECRUXD_CONSOLE_DEV_PATH reads from a developer directory (console.rs:527).

The practical consequence: GET /v1/facts is a general-purpose inspection tool for far more of the daemon's state than its name suggests.

6.5 Delete this and the daemon breaks

Ordered by blast radius.

FileWhat breaksRecoverable?
facts.jsonlTotal memory loss. Every fact, coordination announce, punchcard, extension grant, session binding and legal hold is gone. The daemon starts clean and healthy and will not tell you anything is missingOnly from a backup, or from a remote if sync is configured
shards/shard-NNNN/MANIFESTThe shard reopens with an empty catalogue, so every .ccxseg in segments/ becomes an orphan and is moved to quarantine/ on the next open (lib.rs:1479). All appended events become unreachableThe segment bytes survive in quarantine/, but there is no supported rebuild-from-segments path
shards/shard-NNNN/segments/*.ccxsegData loss for those segments; MANIFEST validation fails on openNo
passport.keyThe daemon mints a new identity. Previously-signed receipts no longer verify against the advertised public key; the anonymous passport claim and every issued capability token are orphaned; stored integration credentials become undecryptableNo, a fresh key is generated silently at passport.rs:232
passports/<id>.keyThat passport's signing identity is regenerated; tokens and receipts signed by the old key stop verifyingNo
meta/node.jsonA new node_id on the next boot. Shard-map entries pointing at the old id go stale, and the replicated_commit_topology readiness gate can failNo
meta/routing/currentFalls back to initialising a fresh default dev shard map (shard_map.rs:126), which will not match the existing shard directoriesPartially, the shardmap.v*.json files are still there and current can be hand-restored
projections.meta.jsonProjection cursors reset to zero; a full replay is required, and row counts and commit_id restartYes, by replay, at the cost of a full rescan
CONTROL.jsonOperator valves reset to defaults, an emergency_brake or read_only you set is silently clearedNo
audit-export-signing.keyPreviously-exported audit bundles no longer verify against the current keyNo
.install-uuidThe session-plane install identity changes; the daemon reports as a different install to any collectorNo

Safe to delete, regenerated or purely additive: LOCK, shards/*/LOCK, meta/routing/LOCK and console/chunks-index.lock, all only while the daemon is stopped; shards/*/quarantine/*; shards/*/tmp/*; console/chunks-index.json; and the fastembed model cache.

A minimal backup set, if you cannot take the whole directory: facts.jsonl, passport.key, passports/, CONTROL.json, meta/, shards/, audit-export-signing.key, integrations/, .install-uuid.

6.6 The four locks

LockPathMechanismGuardsHeld for
Daemon instance lock<data_dir>/LOCKtry_lock_exclusive: non-blocking, fails startupThe entire data dir: one corecruxd per data dir. Feeds the data_dir_lock_held readiness gateProcess lifetime
Shard lock<data_dir>/shards/shard-NNNN/LOCKtry_lock_exclusive with a ten-times-5 ms retry for the deferred-fput windowOne writer per shard: MANIFEST, segments, directory, projectionsWhile the shard handle is open
Shard-map publish lock<data_dir>/meta/routing/LOCKlock_exclusive, blockingThe atomic shard-map publish protocolOne publish
Console index lock<data_dir>/console/chunks-index.locklock_exclusive, blocking, explicitly unlockedRead-modify-write of console/chunks-index.json across concurrent HTTP requestsOne index mutation

Two locks are in-process, not on disk: the session-plane sealer uses a Mutex (sealer.rs:117), and the audit-signing-key creator a process-wide OnceLock<Mutex<()>> (audit_signing_key.rs:120).

6.7 What grows without bound

Six artefacts have no automatic reclamation path at all. On a long-lived daemon they are the reason the disk fills, and a full disk takes the daemon out of rotation via the data_dir_capacity readiness gate, see chapter 9 §9.5.

ArtefactGrowth driverGC pathDefault
facts.jsonlEvery store, delete and supersede. The journal never shrinks on its ownEphemeral GC (ephemeral_gc.rs), plus operator-triggered compact_journalEphemeral GC off; compaction never automatic
__session_binding__::* factsOne durable fact per MCP session. A stateless bridge that re-initialises per poll accumulates without boundEphemeral GC keeps the newest 32 per passport and collects the rest once older than 1 hourOff by default
__reverify_receipts__::* factsMinted by memory_reverifyEphemeral GC deletes when older than 30 daysOff by default
observations/*.jsonlEvery signed observation, receipt and ledger row. One file per session id, so the file count grows with session count tooNone. No rotation, no retention sweep, no archive. Only the per-record payload is capped-
shards/*/quarantine/Every crash-recovery sweep on shard open. Filenames are timestamp-prefixed so nothing is overwrittenNone. No code path deletes from quarantine/. Reap it manually-
meta/routing/shardmap.v*.jsonOne immutable file per shard-map versionNone-
integrations/audit.jsonlEvery pack and extension actionNone, reads are tail-only-
activity/journal.jsonlEvery activity-log entryNoneFlag-gated
cost/reports.jsonlEvery POST /v1/cost/reportNoneFlag-gated
session-events.jsonlEvery sealed session event, fsync per appendNone-
sessions/*.jsonOne file per issued sessionExpiry is enforced in the registry; on-disk cleanup follows the registry's removal path, not a scheduled sweep-
cold/*/segments/*Projection cold spillgc_cold_segments_dir_v1 with min_age_seconds, max_delete and dry_run, the one real segment GC in the treeCaller-driven
witness_proofs.jsonl, entities.jsonl, substrate-edges.jsonl, sessions.jsonl, cases.jsonl, relations.jsonl, credit-meter.jsonl, sync-outbox.jsonlAppend-only journalsNone. There is no compaction equivalent to the fact journal-

CORECRUXD_OBS_RETENTION_DAYS archives observation sessions but is unset by default, i.e. retain forever (main.rs:1223).

The ephemeral GC, in detail

  • Gate: CORECRUXD_EPHEMERAL_GC, default off. Read once at boot, toggling requires a restart (ephemeral_gc.rs:194).
  • Schedule: hourly. The immediate first tick is skipped so a sweep never runs mid-replay (ephemeral_gc.rs:215).
  • Scope: exactly two reserved entity prefixes, matched by name, __reverify_receipts__:: and __session_binding__:: (ephemeral_gc.rs:98). Non-reserved user facts are never eligible, private or not.
  • Mechanism: a soft delete through the journalled FactStore::try_delete (ephemeral_gc.rs:153). It never touches the filesystem. It appends a Delete tombstone, so the fact stays visible with deleted = true, reversible and replay-safe.
  • Receipt: a non-empty sweep mints a signed receipt into observations/__governance__::gc.jsonl carrying only {deleted, retain_days, reason_code, swept_at, run_id}, never swept content (ephemeral_gc.rs:167). A mint failure bumps an audit-debt counter and logs at error level; it is never silent.

6.8 The capacity guard writes to your control file

CONTROL.json is not only operator-owned. On reaching the emergency free-space threshold, the background capacity guard sets valves.pauseIngest with actor = "capacity_guard" and a reason string, and persists it (main.rs:2506). It takes ownership only if the valve is currently disabled or already guard-owned (main.rs:2510); it will not stomp an operator-set pause.

If you find ingest paused with actor: "capacity_guard", look at disk free ratio, not at your own actions.

Capacity classification uses strictly-less-than comparisons against free_ratio (main.rs:2424):

LevelConditionConfig default
emergencyfree_ratio < capacity_emergency_free_ratio0.10
criticalfree_ratio < capacity_critical_free_ratio0.10
warningfree_ratio < capacity_warning_free_ratio0.20
healthyotherwise-

Free space is measured with fs2::total_space and fs2::available_space on the data dir (main.rs:2436), note available space, not raw free space. A measurement failure zeroes the gauges, records the error, and clears auto_paused.

The four ratios are re-ordered after parsing so they can never be inconsistent (config.rs:1117). Raising EMERGENCY to 0.5 while leaving WARNING at its default therefore raises warning to 0.5 too, rather than producing an impossible ordering.

Sources