Crux Daemon · 9. Observability

Three probe endpoints, 142 Prometheus metrics, and nine readiness gates that must all pass for a 200 on /readyz. The gate that bites in practice is data_dir_capacity at 10% free, §9.6 explains why it makes unrelated things fail with an unhelpful message.

This chapter is reference.

9.0 In plain English

Observability is the set of ways the daemon tells you how it is doing without you having to ask it a question about your data. It comes in three forms here. Logs are what it says as things happen. Metrics are counters and gauges scraped by a monitoring system, 142 of them, exposed at /metrics in Prometheus format. Probes are the two endpoints a load balancer or an orchestrator calls to decide whether this process should receive traffic.

The two probes are not interchangeable, and confusing them is the most common mistake made here. /healthz is liveness: it answers "is this process alive and responding", it always returns 200, and nothing in the handler can make it fail. It is useless as a readiness signal because it cannot say no. /readyz is readiness: it runs nine independent gates and returns 503 with a per-gate breakdown if any one of them fails. If you have wired an orchestrator to /healthz expecting it to catch a sick instance, it will never catch anything.

You will touch this chapter when you first wire the daemon into a monitoring system, and then again the first time something goes wrong in a way the error message does not explain. That second visit is what §9.6 is really for. The gate that bites in practice is data_dir_capacity, which fails when the data partition drops below 10% free. When it trips, the daemon starts returning 503 on readiness, and everything downstream fails in ways that have nothing to do with disk: tests time out waiting for a healthy daemon, clients see connection failures, and the messages they print name none of it. The habit worth building is to check free disk before you believe any other theory.

The one thing people get wrong beyond the two probes: they assume a green start means logs are complete. Nothing is logged before step 10 of the boot sequence, which means the entire config parse and every auth-posture decision happen in silence. If a flag you set had no effect, no log line will tell you so, because the code that read it ran before logging existed. §9.8 gathers this and the rest of what observability here does not give you, including the fact that /metrics is unauthenticated and does expose shard ids, node topology, valve state and hashed tenant ids to anyone who can reach the port.

9.1 Logging

Setup is init_tracing (main.rs:2065), called from main at main.rs:421, step 10 of the boot sequence. Nothing is logged before that point, which includes the entire config parse and every auth-posture rail.

Level

// crates/corecruxd/src/main.rs:2066
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
    .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(level));
KnobEnv varDefaultNotes
Filter directivesRUST_LOGunsettry_from_default_env() reads it. Full EnvFilter syntax, e.g. corecruxd=debug,tower_http=warn. It wins outright over CORECRUXD_LOG_LEVEL
Fallback levelCORECRUXD_LOG_LEVELinfoConsulted only when RUST_LOG is unset or unparseable (config.rs:843)

Format

// crates/corecruxd/src/main.rs:2068
let log_format = std::env::var("LOG_FORMAT").unwrap_or_default();

json, case-insensitively, selects the JSON layer (main.rs:2129). Anything else gives human-readable text.

The variable is LOG_FORMAT, unprefixed. CORECRUX_LOG_FORMAT is read by no code anywhere, yet it is what the Dockerfile, the Helm chart, both compose files, config.example.env and the quickstart README all set or document. JSON logging is silently off in every shipped manifest. See chapter 16 defect B1.

Sink-boundary redaction

Every formatted event is scrubbed before it reaches stdout, through a RedactMakeWriter wrapping std::io::stdout (main.rs:2072).

KnobValuesDefault
CORECRUXD_REDACTon/1/true/enforce redacts; off/0/false/disabled passes through; audit counts hits without mutating. Unknown values fall back to auditaudit
CORECRUXD_REDACT_EXTRA_PATTERNS;;-separated id=regex, e.g. myco=MYCO-[0-9]{6};;legacy=LK_[a-f0-9]{32}. Invalid entries are warn-logged and droppedunset

The default counts but does not redact. Out of the box the daemon tells you how much redactable material is in its logs and ships it anyway. If you send logs off-box, set CORECRUXD_REDACT=on. The counter corecrux_log_redactions_total{rule} increments in both modes, which makes audit the correct pre-flight before switching to on.

The redactor is a process singleton published globally so other crates in the process scrub with the same instance (redaction.rs:41).

/v1/observe/* has its own, stricter knob: CORECRUXD_OBSERVE_REDACT defaults to on (observe_audit.rs:241).

Panics

A std::panic::set_hook emits a structured tracing::error! carrying panic.payload and panic.location (main.rs:457). Axum handler panics are caught separately and returned as an application/problem+json 500, with tracing::error!(panic = %msg, "handler panicked") (health.rs:365).

The structured operations log

structured_log.rs defines a one-JSON-line-per-operation record. Fields (structured_log.rs:127): ts (RFC3339 with milliseconds, UTC), level, request_id?, trace_id?, traceparent?, op, outcome, took_ms, shard_id?, epoch?, error_code?, retryable?, retry_after_ms?, error_detail?, payload_hash_prefix?. Optionals are omitted when absent.

  • error_code uses the log taxonomy, not the HTTP one. See chapter 8 §8.6.
  • Correlation ids: x-request-id and traceparent are lifted from HTTP headers or gRPC metadata and sanitised (structured_log.rs:49), no ASCII control characters, non-empty, at most 128 characters, restricted to [A-Za-z0-9] plus - _ . : / =. A traceparent additionally needs at least 16 characters. A missing or rejected x-request-id is replaced with a fresh UUIDv4.
  • payload_hash_prefix is the first 12 hex characters of BLAKE3 over the payload (structured_log.rs:124). Payloads themselves are never logged.

9.2 The metrics endpoint

PropertyValue
PathGET /metrics (http/mod.rs:475)
Content-Typetext/plain; version=0.0.4; charset=utf-8 (health.rs:352)
Encoderprometheus::TextEncoder over registry.gather() (metrics.rs:2177)
AuthNone. Classified Public with an empty scope set (route_auth.rs:79)
On failure500 application/problem+json

/metrics is unauthenticated and is not covered by CORECRUXD_PUBLIC_PROBES_MINIMAL; that flag trims only /healthz and /readyz. Metric labels expose shard ids, node topology, valve state and hashed tenant ids. Restrict /metrics at the network layer if the daemon is reachable beyond loopback.

A single prometheus::Registry is created at metrics.rs:159 and shared, so every subsystem registers against the same scrape.

Total: 142 metrics, across four registration sites, all of which run unconditionally at boot.

Registration siteCountCalled from
Metrics::new, core daemon120main.rs:488
crux_mcp::ledger::register_metrics10main.rs:832
SessionMetrics::new11main.rs:833
redaction::register_metrics1main.rs:489

A naming inconsistency worth knowing before you write a dashboard query: build_info has no prefix, crux_witness_unwitnessed_heads uses crux_, the eleven session-plane metrics use vaultcrux_, and everything else uses corecrux_.

9.3 Core daemon metrics (1 to 60)

All registered in metrics.rs; the line is the constructor line.

#MetricTypeLabelsMeasuresRegistered
1build_infoGaugeVecversion, commit, serviceBuild metadata:161
2corecrux_build_infoGaugeVecversion, commit, service, sdkVersionBuild metadata, hardening contract:175
3corecrux_io_backendGaugeVecbackendSelected IO backend:195
4corecrux_peer_cache_hits_totalCounter-Peer cache hits, sealed immutable blocks only:204
5corecrux_peer_cache_misses_totalCounter-Peer cache misses:213
6corecrux_peer_cache_bytesGauge-Peer cache size, best-effort:222
7corecrux_tail_cache_hits_totalCounterVecshardTail cache hits:231
8corecrux_tail_cache_misses_totalCounterVecshardTail cache misses:240
9corecrux_tail_cache_bytesGaugeVecshardTail cache resident bytes:249
10corecrux_valve_pause_ingestGauge-Operator valve state:258
11corecrux_http_inflightGauge-HTTP requests past the concurrency gate:264
12corecrux_http_rate_limited_totalCounterVeckey_kindRequests rejected by the rate limiter:273
13corecrux_valve_pause_compactionGauge-Operator valve state:285
14corecrux_valve_throttleGauge-Operator valve state:294
15corecrux_valve_read_onlyGauge-Operator valve state:300
16corecrux_valve_emergency_brakeGauge-Operator valve state:306
17corecrux_valve_stateGaugeVecvalveOne series per valve:315
18corecrux_throttle_ratioGauge-Token-bucket fullness, 1 means no pressure:324
19corecrux_data_dir_bytes_totalGauge-Data directory total bytes:333
20corecrux_data_dir_bytes_freeGauge-Data directory free bytes:339
21corecrux_data_dir_free_ratioGauge-Alert on this one. Free ratio:345
22corecrux_write_confirmations_totalCounterVecsignedWrite confirmations by signed state:351
23corecrux_write_confirmation_sign_duration_msHistogram-Write-confirmation signing latency:363
24corecrux_write_confirmation_unsigned_queue_depthGauge-Unsigned confirmations pending re-sign:372
25corecrux_tenant_throttle_rejected_totalCounterVectenant_id_hashTenant throttle rejections:381
26corecrux_emergency_brake_totalCounterVecsourceEmergency-brake activations:393
27corecrux_write_rejects_totalCounterVecreasonWrite rejects by reason:405
28corecrux_backpressure_active_gaugeGauge-Backpressure active state:417
29corecrux_replay_totalCounterVecresultReplay attempts:424
30corecrux_replay_mismatch_totalCounterVecdrift_classReplay mismatches by drift class:433
31corecrux_segment_corrupt_totalCounterVecreasonDetected segment corruption:442
32corecrux_verify_store_secondsHistogram-verify-store run duration:454
33corecrux_segment_scrub_secondsHistogram-Segment scrub run duration:463
34corecrux_dir_l0_runsGaugeVecshardDirectory L0 run count:472
35corecrux_dir_level_bytesGaugeVecshard, levelDirectory run bytes per level:481
36corecrux_dir_compactions_totalCounterVecshard, level_from, level_to, statusDirectory compactions:493
37corecrux_dir_compaction_secondsHistogramVecshard, level_from, level_toCompaction duration:505
38corecrux_dir_compaction_bytes_in_totalCounterVecshardBytes read as compaction input:517
39corecrux_dir_compaction_bytes_out_totalCounterVecshardBytes published as output:529
40corecrux_dir_dead_extent_ratioGaugeVecshardDead extent ratio:541
41corecrux_checkpoints_installed_totalCounterVecshard, stream_typeCheckpoint installs:553
42corecrux_checkpoint_min_live_seqGaugeVecshard, stream_typeLatest installed min_live_seq:565
43corecrux_stream_tombstones_totalCounterVecshardStream tombstones installed:577
44corecrux_stream_tombstone_rejects_totalCounterVecshardAppends rejected on a tombstoned stream:589
45corecrux_append_latency_secondsHistogramVecshardAppend latency:601
46corecrux_stream_read_latency_secondsHistogramVecshard, opStream read latency:613
47corecrux_read_retry_totalCounterVecop, reason, outcomeRead retries. Feeds the read_retry_failed_threshold readiness gate:625
48corecrux_store_lock_wait_secondsHistogramVecopTime waiting for the store lock:634
49corecrux_store_lock_hold_secondsHistogramVecopTime holding the store lock:646
50corecrux_store_service_secondsHistogramVecopStore service time excluding lock wait:658
51corecrux_append_lane_waitersGauge-Appends waiting for a lane lock:670
52corecrux_append_lane_waiters_peakGauge-Peak concurrent lane waiters:679
53corecrux_append_lane_queue_depthHistogram-Lane queue depth at enqueue:688
54corecrux_append_lane_selected_totalCounterVecbucketAppends selected into fairness buckets:697
55corecrux_append_lane_wait_seconds_by_bucketHistogramVecbucketLane wait time by bucket:709
56corecrux_grpc_messages_sent_totalCounterVecrpcgRPC response messages sent:721
57corecrux_grpc_send_secondsHistogramVecrpcgRPC send and encode duration:733
58corecrux_grpc_send_blocked_secondsHistogramVecrpcgRPC send blocking duration:742
59corecrux_replay_events_totalCounterVecrpcReplay events returned:754
60corecrux_replay_bytes_totalCounterVecrpcReplay bytes returned:763

The gRPC-labelled metrics exist but stay at zero in this build, because every gRPC RPC returns unimplemented, see chapter 1 §1.4.

9.4 Core daemon metrics (61 to 120)

#MetricTypeLabelsMeasuresRegistered
61corecrux_replay_build_response_secondsHistogramVecrpcReplay response materialisation time:772
62corecrux_replay_encode_secondsHistogramVecrpcReplay protobuf encode sampling:784
63corecrux_rpc_total_secondsHistogramVecrpcTotal server-side time per RPC:796
64corecrux_storage_tail_stage_secondsHistogramVecstageTail-read stage duration: index_lookup, io, decode, total:808
65corecrux_storage_append_stage_secondsHistogramVecstageAppend stage duration: idempotency_check, index_update, io_write, fence_wait, fence_fsync, fence, total:820
66corecrux_append_fence_wait_secondsHistogramVecshardAppend durability-fence wait:832
67corecrux_append_fence_fsync_secondsHistogramVecshardAppend fsync time:844
68corecrux_storage_tail_bytes_totalCounterVeckindTail-read bytes: disk_estimate, frame:856
69corecrux_storage_tail_items_totalCounterVeckindTail-read items touched: segments, blocks, frames:868
70corecrux_storage_tail_path_totalCounterVecpath, outcomeTail-read fast-path outcomes:880
71corecrux_storage_head_frames_scanned_totalCounter-Head frames inspected while serving tail reads:892
72corecrux_read_amplification_p50GaugeVecshardRead amplification p50, rolling:901
73corecrux_read_amplification_p95GaugeVecshardRead amplification p95, rolling:913
74corecrux_kernel_launch_totalCounterVeckernel, resultKernel launches and outcomes:925
75corecrux_shardmap_versionGauge-Shard-map version loaded by this process:934
76corecrux_routing_lookup_totalCounterVecop, outcomeRouting lookups:943
77corecrux_routing_lookup_secondsHistogramVecopRouting lookup duration:952
78corecrux_shard_requests_totalCounterVecshardId, opRequests routed to a shard:961
79corecrux_replication_receive_totalCounterVecresultReplication segment receive and apply outcomes:973
80corecrux_replication_follower_watermark_segment_seqGaugeVecshardIdFollower-applied highest segment_seq:985
81corecrux_replicated_commit_totalCounterVecresultReplicatedCommit outcomes:997
82corecrux_replicated_commit_required_acksGaugeVecshardIdRequired acknowledgements:1006
83corecrux_replicated_commit_actual_acksGaugeVecshardIdObserved acknowledgements:1018
84corecrux_replicated_commit_ack_deficitGaugeVecshardIdRequired minus actual:1030
85corecrux_replication_shard_epochGaugeVecshardIdCurrent shard epoch:1042
86corecrux_replication_follower_targetsGaugeVecshardIdConfigured follower count, excluding self:1054
87corecrux_replication_topology_okGaugeVecshardIdTopology sanity:1066
88corecrux_replication_leader_segment_seqGaugeVecshardIdLatest leader segment_seq for shipping:1078
89corecrux_replication_min_follower_acked_segment_seqGaugeVecshardIdMinimum follower-acked segment_seq:1090
90corecrux_replication_lag_segmentsGaugeVecshardIdLag in segment_seq units:1102
91corecrux_shard_stateGaugeVecshardId, stateShard state one-hot: active, draining, retired:1114
92corecrux_projections_commit_idGaugeVecshardLatest projections commit_id:1126
93corecrux_projections_cursor_segment_seqGaugeVecshard, projectionProjection cursor segment_seq:1138
94corecrux_projections_cursor_offsetGaugeVecshard, projectionProjection cursor offset:1150
95corecrux_projections_row_countGaugeVecshard, projectionCommitted projection row count:1162
96corecrux_projections_tick_frames_totalCounterVecshardFrames processed by projection ticks:1174
97corecrux_projections_tick_secondsHistogramVecshardProjection tick duration:1186
98corecrux_projections_tick_fail_totalCounterVecshardProjection tick failures:1198
99corecrux_shard_open_attempts_totalCounterVeccallerShardStorage::open() calls by caller context:1210
100corecrux_lock_contention_totalCounterVeccallerFile-lock contention events:1222
101corecrux_projection_snapshot_validGaugeVecprojectionSnapshot validity per required projection. Set on every /readyz call:1234
102corecrux_knowledge_authority_modeGaugeVecmodeKnowledge authority mode one-hot:1246
103corecrux_knowledge_rollout_stageGaugeVecstageKnowledge rollout stage one-hot:1258
104corecrux_knowledge_parity_statusGaugeVecstatusLast knowledge-parity status one-hot:1270
105corecrux_knowledge_rollback_triggeredGauge-Rollback trigger active:1282
106corecrux_knowledge_parity_mismatch_countGauge-Last observed parity mismatch count:1291
107corecrux_knowledge_parity_cursor_missing_countGauge-Last observed missing-cursor count:1300
108corecrux_knowledge_parity_pass_ratio_bpsGauge-Last observed parity pass ratio, basis points:1309
109corecrux_knowledge_parity_projection_lag_msGauge-Last observed parity projection lag:1318
110corecrux_receipt_verify_totalCounterVecresultReceipt signature verification outcomes:1327
111corecrux_receipt_verify_fail_totalCounterVecreasonReceipt verification failures by reason:1339
112corecrux_receipt_export_totalCounterVecstatusReceipt export bundle requests:1351
113corecrux_query_graph_expand_duration_secondsHistogram-Graph-expand query duration:1364
114corecrux_query_graph_expand_nodes_visitedHistogram-Nodes visited per graph-expand query:1376
115corecrux_query_time_range_duration_secondsHistogram-Time-range query duration:1388
116corecrux_query_time_range_artifacts_scannedHistogram-Artifacts scanned per time-range query:1400
117corecrux_seal_duration_secondsHistogramVecphaseTime to seal a segment, including the .ccxi build:1413
118corecrux_seal_backlog_framesGauge-Frames in the head segment not yet sealed:1426
119corecrux_ccxi_missing_totalGauge-Sealed segments missing a .ccxi companion:1435
120crux_witness_unwitnessed_headsGauge-Seal-chain heads sealed but not yet witnessed:1444

MCP tool-ledger metrics (121 to 130)

Registered in ledger.rs.

#MetricTypeLabelsMeasuresRegistered
121corecrux_tool_invocation_duration_secondsHistogramVectool, outcomeMCP tools/call dispatch latency:246
122corecrux_token_spend_totalCounterVectoolEstimated tokens per tool, arguments plus result:257
123corecrux_tool_response_truncated_totalCounterVectool, reasonResponses truncated by a budget-honouring path:265
124corecrux_tool_ledger_emit_failures_totalCounterVecreasonLedger observation appends that failed. Never fails the tool call:273
125corecrux_coverage_events_without_receiptIntGauge-Events in the last attested window with no receipt:281
126corecrux_coverage_receipts_without_anchorIntGauge-Receipt bodies with no external anchor:286
127corecrux_coverage_gaps_totalIntGauge-Total gaps:291
128corecrux_coverage_events_totalIntGauge-Events covered by the last attested window:296
129corecrux_coverage_receipts_totalIntGauge-Receipts covered by the last attested window:301
130corecrux_coverage_anchored_totalIntGauge-Anchored receipts in the last attested window:306

Session-plane metrics (131 to 141)

Registered in session_metrics.rs. These use the vaultcrux_ prefix.

#MetricTypeLabelsMeasuresRegistered
131vaultcrux_session_handshakes_totalCounterVecorigin, outcomeSession handshake requests:34
132vaultcrux_session_handshake_latency_secondsHistogramVecoriginEnd-to-end handshake latency:46
133vaultcrux_session_capability_graph_sizeHistogramVecorigin, tierCapabilities in issued session plans:59
134vaultcrux_session_activeGauge-Currently-active sessions in the local registry:72
135vaultcrux_session_expired_totalCounterVecorigin, reasonSessions removed: ttl_expired, client_closed, admin_closed:79
136vaultcrux_session_plan_bytesHistogramVecencodingSize of issued session plans:91
137vaultcrux_invocation_receipts_totalCounterVecchannel, capability, outcomePer-capability invocation receipt counts:101
138vaultcrux_invocation_receipt_latency_secondsHistogramVecchannel, capabilityPer-capability invocation latency:113
139vaultcrux_invocation_verify_totalCounterVecoutcomePOST /invocation/verify outcomes:126
140vaultcrux_session_plan_sealer_errors_totalGauge-Cumulative segment-seal errors during session mint:135
141vaultcrux_session_segment_seal_failures_totalGauge-Cumulative always-store seal failures that failed a handshake closed:144

Log-redaction metric (142)

#MetricTypeLabelsMeasuresRegistered
142corecrux_log_redactions_totalCounterVecruleRedaction-rule hits at the log sink. Increments in both on and audit modes, which makes it the pre-flight signal before switching to on:53

Registration is idempotent, a double registration is logged at debug and ignored.

9.5 Tracing and OpenTelemetry

PropertyValue
Cargo featureotel, not in default (Cargo.toml:10-16)
EndpointOTEL_EXPORTER_OTLP_ENDPOINT (main.rs:2085)
TransportOTLP over gRPC (tonic) (main.rs:2087)
ExporterBatch span exporter (main.rs:2093)
Resourceservice.name = "corecruxd", hard-coded, not CORECRUXD_SERVICE (main.rs:2094)
PropagatorW3C TraceContextPropagator, set globally (main.rs:2103)
ShutdownFlushed on SIGINT and SIGTERM before the shutdown broadcast (main.rs:2147)

Behaviour matrix:

BuildEndpoint set?Result
default, no otelanythingThe whole block is compiled out (main.rs:2077). Plain fmt subscriber; the variable is ignored
--features otelunsetFalls through to the same plain subscriber. No OTLP layer, no error
--features otelset, exporter buildsLogs and spans
--features otelset, exporter build failsSilently falls through to the plain subscriber (main.rs:2087). A typo'd endpoint produces no diagnostic

In the otel path the log format is still selected by LOG_FORMAT, and the redacting writer still applies.

You can correlate traces without compiling otel. Even in the default build, x-request-id and traceparent are extracted, sanitised and surfaced in the structured operations log (structured_log.rs:85). Distributed-trace correlation by log join is available out of the box.

9.5.1 corruption_state_clear is a one-way latch, and only a restart clears it

Gate 7 deserves its own note, because the obvious assumption about it is wrong and the consequence is a permanent outage.

corruption_detected is in-process memory, not a file and not a database row, an Arc<RwLock<bool>> on the shared application state (mod.rs:366), initialised to false at boot (main.rs:598).

Exactly two code paths write it, both in the admin plane and both setting it to true (admin.rs:505, admin.rs:557). No production code path anywhere sets it back to false.

And in the Community Edition it cannot be tripped at all. Both set-sites sit behind a dataplane pool that this edition hard-wires to None (main.rs:565), so verify-store and scrub return dataplane disabled (admin.rs:508) and never reach the line that sets the flag. Gate 7 therefore reports clear on a CE daemon because nothing can set it, not because the store has been checked. Do not read a green gate 7 here as evidence of integrity; run corecruxctl verify-store --strict and read its output.

Where the dataplane IS wired, the honest operational statement is:

  • Once verify-store or scrub sets the flag, /readyz returns 503 for the lifetime of the process.
  • There is no endpoint, CLI command or configuration change that clears it. Any documentation describing this gate as "operator-cleared", including earlier drafts of this table, is wrong.
  • The only way out is to restart the daemon, which resets the flag to false because it is process-local.

That last point cuts both ways, and an operator needs both halves. A restart clears the alarm without repairing the data: the flag says a scrub found corruption, and restarting throws that finding away. Run corecruxctl verify-store --strict and resolve what it reports before restarting, or you will have silenced the only signal you had.

9.6 Health, readiness and the nine gates

The daemon exposes exactly three probe endpoints: /healthz, /readyz and /metrics (http/mod.rs:473-475). There is no /livez and no /startupz. All three are unauthenticated. /v1/version is also public; /v1/admin/version requires admin:read.

CORECRUXD_PUBLIC_PROBES_MINIMAL (default off) makes /healthz omit routing and valves, and makes a /readyz failure return {"ok":false,"checks":[]} with the per-gate breakdown withheld (health.rs:64). It does not cover /metrics.

GET /healthz: liveness

Always 200, always ok: true. Nothing in the handler can make it fail (health.rs:23). It is a pure liveness signal, do not use it as a readiness probe.

{
  "ok": true,
  "build":   { "version": "…", "commit": "…" },
  "compat":  { "requires": "…" },
  "sdkVersion": "…",
  "routing": { "shardMapVersion": 1, "shardCount": 4, "lastReloadAt": "…", "nodeId": "…" },
  "valves": {
    "pause_ingest":     { "enabled": false, "actor": "", "reason": "", "updatedAtUnixNs": 0 },
    "pause_compaction": { "…": "…" },
    "throttle":         { "…": "…" },
    "read_only":        { "…": "…" },
    "emergency_brake":  { "…": "…" }
  }
}

In minimal mode routing and valves are omitted.

GET /readyz: readiness

Success is 200 with {"ok": true} (health.rs:270). Failure is 503:

{
  "ok": false,
  "checks": [
    { "name": "data_dir_capacity", "ok": false,
      "error": "data dir free ratio below emergency threshold (free_ratio=0.043 threshold=0.100 free_bytes=37580963840 total_bytes=879609302220)" }
  ]
}

Only failing gates appear in checks, a passing gate is never listed. In minimal mode the array is emptied but the 503 and ok:false remain.

Every readiness gate

Evaluated in this order; all nine must pass for a 200 (health.rs:260).

#namePasses whenThresholdFailure errorSource
1data_dir_lock_heldThe <data_dir>/LOCK flock is heldstructuralLOCK file not heldhealth.rs:274
2routing_loadedThe shard map is non-emptynonerouting table not loadedhealth.rs:281
3replicated_commit_dataplaneNot in ReplicatedCommit, or a dataplane pool exists. Always fails in this build if CORECRUXD_COMMIT_LEVEL=replicated_commit, because the pool is hard-wired to None (main.rs:565)CORECRUXD_COMMIT_LEVEL, default local_commitreplicated commit selected but dataplane store is unavailablehealth.rs:288
4replicated_commit_topologyUnder ReplicatedCommit only: every non-retired shard this node leads has at least one other followershard map contentsreplicated commit requires followers; <N> local leader shard(s) missing followers: …health.rs:295
5read_retry_failed_thresholdFailed context-lost read retries are below the threshold, or the threshold is 0CORECRUXD_READ_RETRY_FAILED_READYZ_THRESHOLD; 0 disablesfailed read retries exceeded threshold (failed=<n> threshold=<t>)health.rs:302
6projection_snapshots_validNo dataplane pool, always true in this build, or no snapshot issues. Side effect: sets corecrux_projection_snapshot_valid for four projections on every callnoneprojection snapshots invalid (…), first four issues then a counthealth.rs:309
7corruption_state_clearNo corruption flag set by verify-store or scrubrestart only, see belowcorruption state set by verify-store/scrubhealth.rs:316
8control_evidence_okControl evidence is not hosted locally, or its verification passed at bootreconciled at main.rs:567the recorded error, else control evidence verification failedhealth.rs:323
9data_dir_capacityMeasurement succeeded, total is above zero, and free_ratio >= emergency_free_ratioCORECRUXD_CAPACITY_EMERGENCY_FREE_RATIO, default 0.10, 10% freethe measurement error, else data dir free ratio below emergency threshold (…)health.rs:330

Gate 9 in full: the one that bites

A data partition below 10% free takes an otherwise-healthy daemon out of rotation. That is correct behaviour and it is also the single most confusing failure in practice, because everything downstream fails with an unhelpful message. Integration tests against such a daemon fail with a bare "not healthy in 10s" and empty stderr; orchestrators mark the pod unready with no application-level error; unrelated features appear broken.

When something inexplicable fails, check df -h on the data partition first.

Measurement is fs2::total_space and fs2::available_space on config.data_dir (main.rs:2436), available space for this user, not raw free space. It is refreshed by the background capacity guard.

Env varMeaningDefaultClamp
CORECRUXD_CAPACITY_GUARD_ENABLEDRun the background guardon-
CORECRUXD_CAPACITY_GUARD_INTERVAL_SECSRe-measure interval3010..=3600
CORECRUXD_CAPACITY_WARNING_FREE_RATIOWarning level0.200.01..=0.95
CORECRUXD_CAPACITY_CRITICAL_FREE_RATIOCritical level0.100.01..=0.90
CORECRUXD_CAPACITY_EMERGENCY_FREE_RATIOThe /readyz gate threshold0.100.01..=0.90
CORECRUXD_CAPACITY_RESUME_FREE_RATIOAuto-resume after an auto-pause0.200.02..=0.99

The four ratios are re-ordered after parsing so they cannot be inconsistent (config.rs:1117). Raising EMERGENCY to 0.5 while leaving WARNING at the default raises warning to 0.5 as well.

Three gauges are updated on every guard tick: corecrux_data_dir_bytes_total, corecrux_data_dir_bytes_free and corecrux_data_dir_free_ratio. Alert on corecrux_data_dir_free_ratio above the emergency threshold and you get warning before /readyz flips.

If the measurement itself fails, the path is gone, for example, the guard sets total, free and ratio to zero and records the error, so gate 9 fails with the measurement error rather than a ratio message.

Note also that the emergency threshold drives the capacity guard's autonomous write to CONTROL.json. See chapter 6 §6.8.

9.7 Version endpoints

GET /v1/version is public (health.rs:491). Body keys: version, msrv, product, cloud_access, agent_workbench, features (text_search, graph_expand, self_observe, mcp, embeddings), capabilities (coordination, consolidation_scheduler, context_surface, local_ingest, auto_capture, status_feed, activity_log, each {enabled: bool}), semantic_profile, protocol_contracts, sync, update.

Three things are deliberately withheld from the public payload (health.rs:516): the build commit, the sync remote_url (the public body exposes only remote_url_redacted: bool), and update commit SHAs and repository directories.

GET /v1/admin/version requires admin:read (health.rs:597) and is a superset: it adds commit, passport{fingerprint, public_key_hex, alg}, cloud, action_enrichment, gpu1_compute, the full sync.remote_url and the full update view.

passport.public_key_hex there is the verification key for every receipt this daemon mints. An auditor holding the observations/*.jsonl files plus that hex can verify offline (health.rs:633).

cloud_access.contract_path and gpu1_compute are null in a stock build, both are hosted-surfaces-gated.

9.8 What observability does not give you

  • No JSON logs from any shipped manifest, because they all set the wrong variable name. Set LOG_FORMAT=json yourself.
  • No redaction by default, audit counts and ships.
  • No authentication on /metrics, and it leaks shard ids, node topology, valve state and hashed tenant ids.
  • No diagnostic when the OTLP exporter fails to build.
  • No /livez and no /startupz. /healthz cannot fail, so it is useless as a readiness signal; use /readyz.
  • No log line at all for the first nine boot steps, including every config-parse and auth-posture decision.
  • No metric for the fact journal's size, watch the disk, not a counter.

Sources