{"record":{"id":"677ce89f4f7a47e7","repo":"rustfs/rustfs","slug":"redaction-refused-the-document-its-node-count-exc","errorCode":null,"errorMessage":"Redaction refused the document: its node count exceeds the frozen budget of 4096.","messagePattern":"Redaction refused the document: its node count exceeds the frozen budget of 4096\\.","errorType":"exception","errorClass":"RedactionError","httpStatus":null,"severity":"error","filePath":"rustfs/src/connect/offline/redaction.rs","lineNumber":170,"sourceCode":"#[serde(rename_all = \"camelCase\")]\npub struct RedactionResult {\n    pub document: Map<String, Value>,\n    pub canonical_json: String,\n    pub redaction_version: &'static str,\n    pub ruleset_hash: &'static str,\n    pub redacted_count: usize,\n    pub counts: RedactionCounts,\n}\n\n#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]\npub enum RedactionError {\n    #[error(\"Redaction refused the document: it names no registered collection surface.\")]\n    UnknownSurface,\n    #[error(\"Redaction refused the document: its size in bytes exceeds the frozen budget of 262144.\")]\n    InputTooLarge,\n    #[error(\"Redaction refused the document: its nesting depth exceeds the frozen budget of 8.\")]\n    TooDeep,\n    #[error(\"Redaction refused the document: its node count exceeds the frozen budget of 4096.\")]\n    TooManyNodes,\n    #[error(\"Redaction refused the document: it is not representable as JSON.\")]\n    NotRepresentable,\n}\n\npub(super) fn redact(source: RedactionSource, document: &Map<String, Value>) -> Result<RedactionResult, RedactionError> {\n    let encoded = serde_json::to_vec(document).map_err(|_| RedactionError::NotRepresentable)?;\n    if encoded.len() > MAX_INPUT_BYTES {\n        return Err(RedactionError::InputTooLarge);\n    }\n\n    let mut counts = RedactionCounts::default();\n    let allowed = document\n        .iter()\n        .filter_map(|(key, value)| {\n            if source.allows(key) {\n                Some((key.clone(), value.clone()))\n            } else {","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/rustfs/rustfs/blob/201c653dcd34c2a01b9aec5991ed76176b342118/rustfs/src/connect/offline/redaction.rs#L152-L188","documentation":"Redaction counts every map entry, array element, and scalar it visits, and refuses the document once the count passes 4096 (MAX_NODES, redaction.rs:29). The count is taken after unregistered top-level fields are dropped, so only content under allowed fields matters. The budget bounds redaction work deterministically under the frozen D05 contract.","triggerScenarios":"redact walking an allowed field whose value tree exceeds 4096 nodes in total - for example a filesystemSummary or networkSummary holding hundreds of entries each serialized as an object with many keys (count_node at redaction.rs:230 trips).","commonSituations":"Large clusters emitting full per-drive or per-interface detail; producers echoing raw df, mount, or netstat tables into the document; a version change that starts including per-node breakdowns.","solutions":["Aggregate at the producer: send coarse counts and summaries instead of one node per drive or interface.","Cap list lengths (top-N entries) before building the document.","If the surface genuinely needs the detail, chunk it into several documents, each under 4096 nodes."],"exampleFix":"// before\ndoc.insert(\"filesystemSummary\", json!(per_mount_details)); // 4096+ nodes -> TooManyNodes\n\n// after\ndoc.insert(\"filesystemSummary\", json!(summarize_filesystems(&per_mount_details))); // aggregated, small","handlingStrategy":"validation","validationCode":"fn count_nodes(value: &serde_json::Value) -> usize {\n    match value {\n        serde_json::Value::Object(m) => 1 + m.values().map(count_nodes).sum::<usize>(),\n        serde_json::Value::Array(a) => 1 + a.iter().map(count_nodes).sum::<usize>(),\n        _ => 1,\n    }\n}\n// submit only when the allowed fields' subtrees total at most 4096 nodes","typeGuard":null,"tryCatchPattern":"match redact(source, &document) {\n    Err(RedactionError::TooManyNodes) => { /* aggregate or chunk; the same payload will always fail */ }\n    other => other,\n}","preventionTips":["Count nodes of generated documents in producer tests with worst-case cluster fixtures.","Store aggregated summaries, never raw per-device tables.","Remember dropped top-level fields cost nothing: only allowed fields' content is counted."],"tags":["rust","rustfs","connect","redaction","json","size-limit"],"backgroundTag":"json-node-limit-exceeded","analyzedSha":"201c653dcd34c2a01b9aec5991ed76176b342118","analyzedAt":"2026-08-23T16:57:04.676Z","contentChangedAt":"2026-08-23T16:57:04.676Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}