Crux
GuidesObservability

Privacy and redaction

What Crux telemetry keeps, redacts, or omits, including DefinitionRef.source and provider errors.

Crux observability is built to be safe by default, in plain terms: nothing leaves your process until you configure a transport, payload capture can be turned off centrally, a redaction failure drops the record instead of sending it unredacted, and join metadata never carries host filesystem roots or unbounded provider dumps.

Graph capture policy

Configure capture once; every consumer (devtools HTTP transport, subscribeObservability(), diagnostics channel, @use-crux/otel) sees the same sanitized records:

import { config } from "@use-crux/core";

export default config({
  observability: {
    recordInputs: "off", // 'inline' | 'reference' | 'off'
    recordOutputs: "reference",
    redactPaths: ["customer.email", "credentials.session"],
    redactRecord: (record) => {
      // return a replacement record, or null to drop fail-closed
      return record;
    },
  },
});

redactPaths is the one data-only persistence policy shared by runtime feedback, Eval evidence and run files, Review context, Add-to-eval sidecars, and generated Runtime hosts. Paths are relative to the payload being stored. Arrays are transparent, so customers.email redacts email in every item of customers. Crux also always redacts authorization, API-key, token, and secret keys at any depth; those defaults cannot be disabled.

Run crux runtime generate after changing this policy. Generated registries contain the normalized paths for the selected deployment and expose only a secret-free policy fingerprint in the authenticated host manifest. The fingerprint is part of Eval evidence identity, so evidence written under a different policy is never reused. A remote Eval result that would need redaction is rejected before durable storage instead of silently changing its meaning.

Crux Local invalidates the previous generated policy before a refresh and finishes initial generation before exposing feedback and Review mutation routes. Those writes return an actionable 503 while no current, validated snapshot is available; they never fall back to an empty configured policy.

  • inline: embed bounded previews on the record
  • reference: keep identity/size pointers without full bodies where supported
  • off: omit payload-shaped content

Every consumer sees records only after the shared sanitization pass, and a failure inside your redactRecord hook drops the record entirely (fail-closed): a broken redaction never leaks an unredacted payload. The emit-path sanitization steps and the toSafeJsonValue() truncation defaults are documented in the Observability reference.

Use redactRecord for graph-record-specific transformations. Use redactPaths for the stable, portable policy that must also apply outside the live graph transport.

DefinitionRef source

Runtime records may attach:

definitionRefs?: Array<{
  id: string
  kind: ProjectDefinitionKind
  role: DefinitionRefRole
  source?: { file: string; line: number; column?: number }
}>

source is a SanitizedSourceRef:

  • Repo-relative only: absolute host paths are never emitted on the wire
  • No .. traversal: paths that escape the project root are dropped
  • No function names: stack-derived function fields are intentionally never emitted
  • Omitted when unproven: if the runtime cannot prove a safe relative path (missing project root, path outside root, bad line), source is absent entirely

Built-in emitters usually omit source. Historical Run detail resolves location only from the exact named manifest; current-only Catalog views use the current compiler read model separately. When runtime code holds a genuine compiled location, sanitizeDefinitionSource(source, { projectRoot }) is the only supported conversion.

Provider errors

Normalized adapter failures use:

interface CruxProviderError {
  kind: CruxProviderErrorKind;
  code: string; // bounded, namespaced machine id
  retryable: boolean;
  message?: string; // optional, redacted human text
}
FieldRetention rule
kindClosed enum, classification only
codeShort namespaced string (openai.rate_limit, anthropic.stream_completion_failed, crux.timeout.generate, …), enough to debug without raw payloads
retryableBoolean classification only, does not trigger retries by itself
messageOptional; always passed through redactProviderMessage / toSafeJsonValue before storage. Empty after redaction → omitted

There is no providerEvidence bag and no raw response-body field on the public error shape. Prefer code + kind in logs; treat message as best-effort, already-redacted text.

OpenTelemetry

@use-crux/otel projects metadata from the same graph stream and drops known payload attribute keys (text, query, messages, output, …) by default.

  • GenAI message content attributes are off unless captureMessageContent: true or OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true (each content attribute capped at 32KB)
  • Tool spans record toModelOutput() shape/size only: not raw tool results
  • Corpus / ingest identifiers export as hashes, not raw paths or URLs
  • W3C baggage is untrusted input: nothing is copied onto spans unless listed in baggageAttributeAllowlist

Storage and Memory explorers

DevTools Storage and related resource views show wiring, capabilities, and operation summaries. They do not display record values, vector contents, blob bodies, or signed URLs unless the application explicitly enables raw debug inspection.

On this page