{"record":{"id":"f1b62cd23e2f91ee","repo":"astrid-runtime/astrid","slug":"unexpected-kind-in-a-canonical-file-owning-clo","errorCode":null,"errorMessage":"unexpected {kind:?} in a canonical File owning closure","messagePattern":"unexpected (.+?) in a canonical File owning closure","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-storage-chunker-evidence/src/sketch.rs","lineNumber":482,"sourceCode":"        .get(&id)\n        .copied()\n        .ok_or_else(|| anyhow::anyhow!(\"content DAG misses {id:?}\"))?;\n    match record.kind() {\n        ObjectKind::Chunk => {\n            let length = u64::try_from(record.canonical_bytes().len())?;\n            chunks.push(Chunk {\n                id,\n                offset: *offset,\n                length,\n            });\n            *offset = checked_add(*offset, length, \"chunk offset\")?;\n        },\n        ObjectKind::File | ObjectKind::ChunkTree => {\n            for child in record.owning_references() {\n                collect_chunks(child, records, offset, chunks)?;\n            }\n        },\n        kind => bail!(\"unexpected {kind:?} in a canonical File owning closure\"),\n    }\n    Ok(())\n}\n\nfn best_resemblance_candidate(\n    corpus_kind: CorpusKind,\n    sketches: &[Option<MaterializedSketch>],\n    versions: &[Version],\n    inverted: &BTreeMap<[u8; 32], Vec<usize>>,\n    target: usize,\n    sample_size: u16,\n) -> Option<usize> {\n    let mut best: Option<(usize, u64, u64)> = None;\n    let target_sketch = sketches.get(target)?.as_ref()?;\n    let candidates = target_sketch\n        .scores\n        .iter()\n        .filter_map(|score| inverted.get(score))","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-chunker-evidence/src/sketch.rs#L464-L500","documentation":"During construction of a canonical File sketch, `collect_chunks` recursively walks the content DAG from a File record, flattening its owning closure into an ordered chunk list. It only expects to encounter `Chunk` records (leaf data) and `File`/`ChunkTree` records (interior nodes); any other ObjectKind reached through owning references means the DAG contains an object type that cannot legally appear inside a File's owning closure, so the walk aborts rather than silently mis-flatten it.","triggerScenarios":"Calling sketch construction (e.g. `from_built` or another `collect_chunks` entry point) over a content-DAG record map where a File/ChunkTree's `owning_references()` chain includes an object whose `kind()` is neither Chunk, File, nor ChunkTree — e.g. a manifest/metadata-style object accidentally linked as an owning child of a File.","commonSituations":"A storage writer bug or corrupted evidence store links a non-chunk object (e.g. a corpus index record or a newer object kind introduced by a version change) under a File node; hand-edited or migrated object stores referencing records with unexpected kinds.","solutions":["Inspect the offending ObjectId's ObjectRecord and fix the producer that linked it into a File's owning_references — only Chunk and File/ChunkTree nodes belong there.","Verify the record map passed to the sketch builder is complete and from the intended corpus version; reload or rebuild the evidence store if it was migrated.","If a new ObjectKind is legitimately part of File closures, extend collect_chunks's match to handle it explicitly instead of falling into the bail arm.","Run a DAG validation pass over the object store (every owning_reference resolves to Chunk/File/ChunkTree) before sketching."],"exampleFix":"// before\nkind => bail!(\"unexpected {kind:?} in a canonical File owning closure\"),\n// after\nObjectKind::Blob => { /* handle the newly supported kind */ },\nkind => bail!(\"unexpected {kind:?} in a canonical File owning closure\"),","handlingStrategy":"validation","validationCode":"fn validate_file_closure(records: &BTreeMap<ObjectId, &ObjectRecord>, id: ObjectId) -> Result<()> {\n    let mut stack = vec![id];\n    while let Some(id) = stack.pop() {\n        let record = records.get(&id).ok_or_else(|| anyhow::anyhow!(\"missing {id:?}\"))?;\n        match record.kind() {\n            ObjectKind::Chunk => {},\n            ObjectKind::File | ObjectKind::ChunkTree => stack.extend(record.owning_references()),\n            other => bail!(\"illegal {other:?} in File closure at {id:?}\"),\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_legal_closure_kind(kind: ObjectKind) -> bool {\n    matches!(kind, ObjectKind::Chunk | ObjectKind::File | ObjectKind::ChunkTree)\n}","tryCatchPattern":"match collect_chunks(root, &records, &mut off, &mut chunks) {\n    Err(e) if e.to_string().contains(\"in a canonical File owning closure\") => {\n        log::error!(\"store corrupt: non-chunk object linked under a File; rebuild store\");\n    },\n    Err(e) => return Err(e),\n    Ok(()) => {},\n}","preventionTips":["Validate the content DAG (kinds of all owning_references) before sketching.","Only allow the writer API to link Chunk/File/ChunkTree nodes as owning children of Files.","Reject unknown ObjectKind values at store ingestion time."],"tags":["rust","storage","content-addressed","invariant-violation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}