astrid-runtime/astrid · critical

unexpected {kind:?} in a canonical File owning closure

Error message

unexpected {kind:?} in a canonical File owning closure

What it means

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.

Source

Thrown at crates/astrid-storage-chunker-evidence/src/sketch.rs:482

        .get(&id)
        .copied()
        .ok_or_else(|| anyhow::anyhow!("content DAG misses {id:?}"))?;
    match record.kind() {
        ObjectKind::Chunk => {
            let length = u64::try_from(record.canonical_bytes().len())?;
            chunks.push(Chunk {
                id,
                offset: *offset,
                length,
            });
            *offset = checked_add(*offset, length, "chunk offset")?;
        },
        ObjectKind::File | ObjectKind::ChunkTree => {
            for child in record.owning_references() {
                collect_chunks(child, records, offset, chunks)?;
            }
        },
        kind => bail!("unexpected {kind:?} in a canonical File owning closure"),
    }
    Ok(())
}

fn best_resemblance_candidate(
    corpus_kind: CorpusKind,
    sketches: &[Option<MaterializedSketch>],
    versions: &[Version],
    inverted: &BTreeMap<[u8; 32], Vec<usize>>,
    target: usize,
    sample_size: u16,
) -> Option<usize> {
    let mut best: Option<(usize, u64, u64)> = None;
    let target_sketch = sketches.get(target)?.as_ref()?;
    let candidates = target_sketch
        .scores
        .iter()
        .filter_map(|score| inverted.get(score))

View on GitHub (pinned to affd8760f4)

Solutions

  1. 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.
  2. 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.
  3. 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.
  4. Run a DAG validation pass over the object store (every owning_reference resolves to Chunk/File/ChunkTree) before sketching.

Example fix

// before
kind => bail!("unexpected {kind:?} in a canonical File owning closure"),
// after
ObjectKind::Blob => { /* handle the newly supported kind */ },
kind => bail!("unexpected {kind:?} in a canonical File owning closure"),
Defensive patterns

Strategy: validation

Validate before calling

fn validate_file_closure(records: &BTreeMap<ObjectId, &ObjectRecord>, id: ObjectId) -> Result<()> {
    let mut stack = vec![id];
    while let Some(id) = stack.pop() {
        let record = records.get(&id).ok_or_else(|| anyhow::anyhow!("missing {id:?}"))?;
        match record.kind() {
            ObjectKind::Chunk => {},
            ObjectKind::File | ObjectKind::ChunkTree => stack.extend(record.owning_references()),
            other => bail!("illegal {other:?} in File closure at {id:?}"),
        }
    }
    Ok(())
}

Type guard

fn is_legal_closure_kind(kind: ObjectKind) -> bool {
    matches!(kind, ObjectKind::Chunk | ObjectKind::File | ObjectKind::ChunkTree)
}

Try / catch

match collect_chunks(root, &records, &mut off, &mut chunks) {
    Err(e) if e.to_string().contains("in a canonical File owning closure") => {
        log::error!("store corrupt: non-chunk object linked under a File; rebuild store");
    },
    Err(e) => return Err(e),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/f1b62cd23e2f91ee. Report an issue: GitHub.