Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
5069de2
Read the holdout of the published model, not the last-evaluated one
Vedaant-J Oct 21, 2026
ce86b46
Expose published_fit_mode on the artifact test's fake trainer
Vedaant-J Oct 22, 2026
c1586bd
Record a snapshot manifest next to every graph index
Vedaant-J Oct 20, 2026
7d6ecbe
Address snapshot manifest review feedback
Vedaant-J Oct 20, 2026
2e2b01d
Match the backfill diff when no build commit is recorded
Vedaant-J Oct 20, 2026
244b5fe
Record each source table's full location in the snapshot manifest
Vedaant-J Oct 21, 2026
9eff66d
Drop graph_id and data_version from the snapshot manifest; keep the d…
Vedaant-J Oct 21, 2026
aeb0b22
Tighten manifest schema checks and scheduler context
Vedaant-J Oct 21, 2026
16b844f
Drop snapshot-manifest tests of private helpers
Vedaant-J Oct 21, 2026
048473f
Trim snapshot manifest PR to essentials
Vedaant-J Oct 21, 2026
0ecb465
Reuse existing helpers in the snapshot manifest
Vedaant-J Oct 21, 2026
2552bc1
Record DiskGraph's version and build commit in manifests
Vedaant-J Oct 21, 2026
8b16cd1
Type source versions and allow location-free snapshot manifests
Vedaant-J Oct 21, 2026
482b1b2
Pick the source-location lookup by connector type
Vedaant-J Oct 21, 2026
0d73b76
Record Custom SQL text by default; one record_sources switch
Vedaant-J Oct 21, 2026
d2cd1c0
Stamp the engine build commit into wheels; type directory versions
Vedaant-J Oct 21, 2026
72e2932
Carry the record_sources choice on GraphSnapshot
Vedaant-J Oct 22, 2026
60e1ab6
Record Custom SQL's namespace and normalized fingerprint
Vedaant-J Oct 22, 2026
915e10c
Make the snapshot manifest a frozen dataclass
Vedaant-J Oct 22, 2026
bd39c7c
Record training-table lineage across export and reload
Vedaant-J Oct 20, 2026
e9778ae
Digest exported parts at the source for training-table lineage
Vedaant-J Oct 20, 2026
0a49692
Record a custom table's source instead of re-reading its export
Vedaant-J Oct 21, 2026
cf7fea2
Record a custom table's source in the snapshot manifest's shape
Vedaant-J Oct 21, 2026
209e6ee
Keep sidecar writes best-effort; version custom tables from their read
Vedaant-J Oct 21, 2026
80807de
Trim training-table lineage to essentials
Vedaant-J Oct 21, 2026
fcde71e
Read row counts and source mode from DiskGraph's manifest
Vedaant-J Oct 21, 2026
bba451d
Record a custom table's source as the snapshot's manifest does
Vedaant-J Oct 21, 2026
aeab584
Export a training table even when its manifest sidecar fails
Vedaant-J Oct 21, 2026
370351b
Follow the snapshot's record_sources choice
Vedaant-J Oct 21, 2026
f266ec5
Expect the listing kind for a reloaded export directory
Vedaant-J Oct 21, 2026
23f57a2
Record a custom table's source only when the snapshot did
Vedaant-J Oct 22, 2026
f7c62c3
Make the training-table manifest a frozen dataclass
Vedaant-J Oct 22, 2026
d867b57
Record bundle_id and fit provenance in every model bundle
Vedaant-J Oct 20, 2026
4ced958
Address bundle provenance review feedback
Vedaant-J Oct 20, 2026
51c767a
Read Trainer.bundle_id defensively
Vedaant-J Oct 20, 2026
8047610
Record the published model's fit mode in provenance.json
Vedaant-J Oct 20, 2026
f03673d
Mirror the training table's source in bundle provenance
Vedaant-J Oct 21, 2026
5f3dbef
Trim provenance.json to facts no other bundle file holds
Vedaant-J Oct 21, 2026
777bdb4
Record fit_mode and source versions in provenance; expose it on Trainer
Vedaant-J Oct 21, 2026
6eab1d6
Trim bundle provenance to essentials
Vedaant-J Oct 21, 2026
1321f3c
Reuse shared helpers for bundle provenance
Vedaant-J Oct 21, 2026
ef4485c
Compute a fresh bundle's id from its publication manifest
Vedaant-J Oct 21, 2026
fd6e8d7
Copy a custom table's full source entry into provenance
Vedaant-J Oct 21, 2026
e0ad8ac
Store the bundle id in provenance.json
Vedaant-J Oct 21, 2026
4274048
Make bundle provenance a frozen dataclass
Vedaant-J Oct 22, 2026
160f8a6
Add GraphSnapshot export and load through object storage
Vedaant-J Oct 20, 2026
62b034b
Ship the object-store sink in the public connector image
Vedaant-J Oct 20, 2026
4cd042e
Report source changes since a snapshot and warn on stale loads
Vedaant-J Oct 20, 2026
dee6bb2
Match source changes on the recorded connector and location
Vedaant-J Oct 21, 2026
59d62fa
Keep an existing export when a rebuilt snapshot is re-exported
Vedaant-J Oct 21, 2026
ef1a4ad
Pass allow_delayed_open to S3 only when pyarrow supports it
Vedaant-J Oct 21, 2026
a58e325
Trim comments and implementation-detail tests
Vedaant-J Oct 21, 2026
4c9f341
Trim snapshot export PR to essentials
Vedaant-J Oct 21, 2026
6ca9017
Move snapshot export checksums into DiskGraph and reuse its manifest
Vedaant-J Oct 21, 2026
f8563b4
Check loaded snapshots with what DiskGraph already stamps
Vedaant-J Oct 21, 2026
a51952c
Derive GraphSnapshot.graph_key from the snapshot id
Vedaant-J Oct 21, 2026
660c41b
Make snapshot export atomic and pass the snapshot manifest through
Vedaant-J Oct 21, 2026
b583a96
Drop the S3 upload tuning from snapshot export
Vedaant-J Oct 21, 2026
85676da
Repair files a concurrent export wiped before committing
Vedaant-J Oct 21, 2026
f37a3bd
Compare directory listings in source_changes
Vedaant-J Oct 21, 2026
c82556a
Drop the connector-image allowlist entry for the removed sink test
Vedaant-J Oct 21, 2026
48f5796
Raise ConcurrentExportError when another export keeps replacing the d…
Vedaant-J Oct 22, 2026
b6394e2
Read snapshot manifests as dataclasses in export and source_changes
Vedaant-J Oct 22, 2026
d4272b5
Add PredictiveQuery.from_snapshot for graph-free scoring
Vedaant-J Oct 20, 2026
eb0f0db
Expose Trainer.training_query and keep the graph-bound snapshot check
Vedaant-J Oct 21, 2026
568ce32
Trim comments and implementation-detail tests
Vedaant-J Oct 21, 2026
3df8621
Show where a graph-free job gets its snapshot
Vedaant-J Oct 21, 2026
8d23970
Read the native graph and graph id from GraphSnapshot
Vedaant-J Oct 21, 2026
975d144
Add Trainer.predict_snapshot for one-call graph-free scoring
Vedaant-J Oct 21, 2026
2c619ea
Distinguish graph-free scoring in the model reload docs
Vedaant-J Oct 21, 2026
2cc4e5c
Test graph-free scoring of an exported snapshot end to end
Vedaant-J Oct 21, 2026
49bae78
Warn when predict_snapshot scores another graph's snapshot
Vedaant-J Oct 22, 2026
6bdd795
Report fit progress events to callbacks and JSON-lines stdout
Vedaant-J Oct 20, 2026
4c09c57
Bind the trial error name before the progress-event closure
Vedaant-J Oct 20, 2026
bcc7503
Drop the library JSON progress option; callbacks cover it
Vedaant-J Oct 21, 2026
936f289
Report trial_ended only once the trial is recorded as succeeded
Vedaant-J Oct 21, 2026
eebc54a
Carry the error type and a short message on failure events
Vedaant-J Oct 21, 2026
4e7502b
Trim internal docstrings and implementation-detail test checks
Vedaant-J Oct 21, 2026
c1b3e24
Trim fit progress events to essentials
Vedaant-J Oct 21, 2026
18e4d3f
Keep fit_started best-effort when the inputs are malformed
Vedaant-J Oct 21, 2026
8197f42
Reuse the progress logger, searcher and instrumentation for fit events
Vedaant-J Oct 21, 2026
6b5f8eb
Never fail a fit when an epoch event cannot be built
Vedaant-J Oct 21, 2026
6dea3f7
Record Trainer.fit in MLflow with opt-in fit(tracking=)
Vedaant-J Oct 20, 2026
0dfa5d6
Move tracking names into core run records; thin MLflow adapter
Vedaant-J Oct 21, 2026
8473c1d
Keep record building best-effort; declare MLflow's Lightning step axis
Vedaant-J Oct 21, 2026
24bc0ef
Tag model links in core; log per-epoch steps and finish tags in MLflow
Vedaant-J Oct 21, 2026
e61dc62
Drop the removed progress setting from the MLflow test fixture
Vedaant-J Oct 21, 2026
ef0ed18
Record run kind and failures as tags; keep ids out of params
Vedaant-J Oct 21, 2026
a8317fb
Move the tracking adapter API to kumo.engine.tracking
Vedaant-J Oct 21, 2026
00987ae
Stop calling a tracker for the rest of the fit after it raises
Vedaant-J Oct 21, 2026
9972e15
Trim tracking docstrings and tests of internals
Vedaant-J Oct 21, 2026
18a973d
Trim MLflow tracking to essentials
Vedaant-J Oct 21, 2026
5d1c1a5
Build tracking records once in the session; adapters get records
Vedaant-J Oct 21, 2026
8d829a7
Keep experiment tracking off the training thread
Vedaant-J Oct 21, 2026
469742d
Drop the training table id from fit runs
Vedaant-J Oct 21, 2026
529e09c
Record pointers, not lineage, on tracked fit runs
Vedaant-J Oct 21, 2026
9892fc2
Log source tables as native MLflow inputs; cut unused tracking surface
Vedaant-J Oct 21, 2026
2686f5c
Log manifest source tables as MLflow inputs whatever the connector case
Vedaant-J Oct 22, 2026
c9ff49f
Require MLflow 3.13 in the mlflow extra
Vedaant-J Oct 22, 2026
5a78d42
Expose the fit run's id as MlflowTracking.run_id
Vedaant-J Oct 22, 2026
6c1943c
Read fit inputs through the manifest dataclasses
Vedaant-J Oct 22, 2026
b05926f
Record Trainer.fit in Snowflake Experiment Tracking
Vedaant-J Oct 21, 2026
f5eb73f
Expose the fit run's name as SnowflakeTracking.run_id
Vedaant-J Oct 22, 2026
b5b405e
Carry scoring defaults in a derived model bundle
Vedaant-J Oct 20, 2026
75378e4
Normalise scoring defaults and allow a multilabel threshold default
Vedaant-J Oct 21, 2026
f670d50
Trim scoring defaults to essentials
Vedaant-J Oct 21, 2026
fe4ec2a
Reuse bundle_task_type and write_manifest for scoring defaults
Vedaant-J Oct 21, 2026
b8e8d9e
Record a derived bundle's own id in its provenance.json
Vedaant-J Oct 21, 2026
248091b
Record bundle derivation on the provenance dataclass
Vedaant-J Oct 22, 2026
c606def
Accept trailing-slash directories when fetching Databricks models
Vedaant-J Oct 21, 2026
0a07a2d
Add model aliases and a bundle id pointer to model registries
Vedaant-J Oct 21, 2026
18e3b42
Roll back failed Snowflake alias moves; validate aliases before fit
Vedaant-J Oct 21, 2026
f08aeee
Add prediction_output_columns and connector_for_root helpers
Vedaant-J Oct 21, 2026
0b693a1
Log, register and serve fitted models through MLflow pyfunc
Vedaant-J Oct 21, 2026
136d779
Score each pyfunc predict in its own scratch dir; state MLflow 3.13
Vedaant-J Oct 22, 2026
bb34a54
Bound the model-logging wait; give only the upload a longer timeout
Vedaant-J Oct 22, 2026
5255ad9
Read provenance and scheduler context as dataclasses for MLflow
Vedaant-J Oct 22, 2026
4e852aa
Add kumo.job() for scheduled job scripts
Vedaant-J Oct 21, 2026
0a788b2
Print the job summary in finally; exit 0 or non-zero
Vedaant-J Oct 21, 2026
a792a18
Keep at most 20 distinct, truncated warnings in the job summary
Vedaant-J Oct 21, 2026
235a677
Leave deprecation warnings out of the job summary
Vedaant-J Oct 21, 2026
b5d56e0
Do not recurse when another patcher wraps kumo.job()'s showwarning
Vedaant-J Oct 22, 2026
2c18051
Write the job summary's scheduler from SchedulerContext
Vedaant-J Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion 3rdparty/notices/inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@
"rust:cpp-demangle@0.4.5",
"rust:cpufeatures@0.2.17",
"rust:cpufeatures@0.3.0",
"rust:crc32fast@1.5.0",
"rust:crossbeam-deque@0.8.6",
"rust:crossbeam-epoch@0.9.21",
"rust:crossbeam-utils@0.8.21",
Expand Down Expand Up @@ -3642,7 +3643,8 @@
},
{
"artifact_ids": [
"diskgraph-extension"
"diskgraph-extension",
"diskgraph-ingest"
],
"copyright": [
"Copyright (c) 2018 Sam Rijs, Alex Crichton and contributors"
Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/diskgraph/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/diskgraph/diskgraph-ingest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ epserde = { workspace = true }
memmap2 = "0.9"
libc = "0.2"
rayon = "1"
crc32fast = "1.5"
jemallocator = { version = "0.5", optional = true, features = ["profiling"] }
jemalloc-ctl = { version = "0.5", optional = true }
sysinfo = "0.30"
Expand Down
134 changes: 119 additions & 15 deletions packages/diskgraph/diskgraph-ingest/src/snapshot_mirror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use anyhow::{Context, Result};
use object_store::path::Path as ObjectPath;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

use diskgraph_fs::{CloudTransfer, CloudTransferTicket};

Expand Down Expand Up @@ -488,21 +489,9 @@ pub(crate) fn validate_manifest_rel_path(rel: &str) -> Result<(), &'static str>
Ok(())
}

/// Verify the snapshot rooted at `output_dir` against its `_MANIFEST.json`.
/// Used by the consumer side after a Python-driven dataplane→local copy
/// completes, to catch lifecycle-mid-delete races and partial copies
/// before the snapshot is handed to a workload.
///
/// Pass on success; [`CorruptionError`] on any deviation from the
/// manifest contract. Errors are precise about what failed so the
/// caller's alerting carries actionable detail (which file is missing,
/// expected vs actual size, etc.).
pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::Result<(), CorruptionError> {
let success_path = output_dir.join(SUCCESS_MARKER_FILENAME);
if !success_path.exists() {
return Err(CorruptionError::MissingSuccess(success_path));
}

/// Read and validate `_MANIFEST.json` at `output_dir`: schema version and
/// every rel_path's confinement to the snapshot root.
fn read_manifest(output_dir: &std::path::Path) -> std::result::Result<Manifest, CorruptionError> {
let manifest_path = output_dir.join(MANIFEST_FILENAME);
let manifest_bytes = match std::fs::read(&manifest_path) {
Ok(b) => b,
Expand Down Expand Up @@ -548,6 +537,25 @@ pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::R
});
}
}
Ok(manifest)
}

/// Verify the snapshot rooted at `output_dir` against its `_MANIFEST.json`.
/// Used by the consumer side after a Python-driven dataplane→local copy
/// completes, to catch lifecycle-mid-delete races and partial copies
/// before the snapshot is handed to a workload.
///
/// Pass on success; [`CorruptionError`] on any deviation from the
/// manifest contract. Errors are precise about what failed so the
/// caller's alerting carries actionable detail (which file is missing,
/// expected vs actual size, etc.).
pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::Result<(), CorruptionError> {
let success_path = output_dir.join(SUCCESS_MARKER_FILENAME);
if !success_path.exists() {
return Err(CorruptionError::MissingSuccess(success_path));
}

let manifest = read_manifest(output_dir)?;

let mut missing: Vec<String> = Vec::new();
let mut size_mismatches: Vec<(String, u64, u64)> = Vec::new();
Expand Down Expand Up @@ -584,6 +592,71 @@ pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::R
Ok(())
}

/// CRC-32 of every `range_bytes` span of each file `_MANIFEST.json` lists,
/// and of `_MANIFEST.json` itself, keyed by rel_path (an empty file has one
/// zero entry). Lets a copy of the snapshot be checked for content as well
/// as size; the manifest format is unchanged.
pub fn snapshot_range_checksums(
output_dir: &std::path::Path,
range_bytes: u64,
) -> std::result::Result<BTreeMap<String, Vec<u32>>, CorruptionError> {
use rayon::prelude::*;
use std::os::unix::fs::FileExt;

const CHUNK: u64 = 1 << 20;
if range_bytes == 0 {
return Err(CorruptionError::Io {
path: output_dir.to_path_buf(),
detail: "range_bytes must be positive".into(),
});
}
let mut files = read_manifest(output_dir)?.files;
let manifest_size = std::fs::metadata(output_dir.join(MANIFEST_FILENAME))
.map(|meta| meta.len())
.unwrap_or(0);
files.push(ManifestEntry {
rel_path: MANIFEST_FILENAME.into(),
size_bytes: manifest_size,
});
let ranges: Vec<(usize, u64)> = files
.iter()
.enumerate()
.flat_map(|(index, entry)| {
let count = entry.size_bytes.div_ceil(range_bytes).max(1);
(0..count).map(move |range| (index, range))
})
.collect();
let crcs = ranges
.par_iter()
.map(|&(index, range)| {
let entry = &files[index];
let path = output_dir.join(&entry.rel_path);
let io = |e: std::io::Error| CorruptionError::Io {
path: path.clone(),
detail: e.to_string(),
};
let file = std::fs::File::open(&path).map_err(io)?;
let start = range * range_bytes;
let end = entry.size_bytes.min(start + range_bytes);
let mut hasher = crc32fast::Hasher::new();
let mut buffer = vec![0u8; CHUNK.min(end - start) as usize];
let mut offset = start;
while offset < end {
let length = CHUNK.min(end - offset) as usize;
file.read_exact_at(&mut buffer[..length], offset).map_err(io)?;
hasher.update(&buffer[..length]);
offset += length as u64;
}
Ok(hasher.finalize())
})
.collect::<std::result::Result<Vec<u32>, CorruptionError>>()?;
let mut out: BTreeMap<String, Vec<u32>> = BTreeMap::new();
for (&(index, _), crc) in ranges.iter().zip(crcs) {
out.entry(files[index].rel_path.clone()).or_default().push(crc);
}
Ok(out)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -933,4 +1006,35 @@ mod tests {
verify_snapshot_integrity(local_root.path())?;
Ok(())
}

#[test]
fn range_checksums_cover_every_listed_file_and_the_manifest() {
let tmp = tempfile::tempdir().unwrap();
let big: Vec<u8> = (0..10u32).flat_map(|i| i.to_le_bytes()).collect();
write_marker_files(tmp.path(), &[("a.bin", &big), ("nested/empty", b"")]);
write_manifest(
tmp.path(),
vec![
ManifestEntry {
rel_path: "a.bin".into(),
size_bytes: 40,
},
ManifestEntry {
rel_path: "nested/empty".into(),
size_bytes: 0,
},
],
);
let crcs = snapshot_range_checksums(tmp.path(), 16).unwrap();
let manifest = std::fs::read(tmp.path().join(MANIFEST_FILENAME)).unwrap();
assert_eq!(crcs["a.bin"], [&big[..16], &big[16..32], &big[32..]].map(crc32fast::hash));
assert_eq!(crcs["nested/empty"], vec![0]);
assert_eq!(crcs[MANIFEST_FILENAME], manifest.chunks(16).map(crc32fast::hash).collect::<Vec<_>>());

std::fs::write(tmp.path().join("a.bin"), &big[..20]).unwrap();
assert!(matches!(
snapshot_range_checksums(tmp.path(), 16).unwrap_err(),
CorruptionError::Io { .. }
));
}
}
29 changes: 29 additions & 0 deletions packages/diskgraph/diskgraph-pybind/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,37 @@ mod native_cpu_guard {
));
}

fn build_commit() -> String {
println!("cargo:rerun-if-env-changed=DISKGRAPH_BUILD_COMMIT");
if let Ok(commit) = std::env::var("DISKGRAPH_BUILD_COMMIT") {
return commit;
}
let git = |args: &[&str]| {
std::process::Command::new("git")
.args(args)
.output()
.ok()
.filter(|out| out.status.success())
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_owned())
.filter(|out| !out.is_empty())
};
for name in ["HEAD", "packed-refs"] {
if let Some(path) = git(&["rev-parse", "--git-path", name]) {
println!("cargo:rerun-if-changed={path}");
}
}
if let Some(head) = git(&["symbolic-ref", "-q", "HEAD"]) {
if let Some(path) = git(&["rev-parse", "--git-path", &head]) {
println!("cargo:rerun-if-changed={path}");
}
}
git(&["rev-parse", "--short=12", "HEAD"]).unwrap_or_else(|| "unknown".into())
}

fn main() {
pyo3_build_config::use_pyo3_cfgs();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=DISKGRAPH_BUILD_COMMIT={}", build_commit());

if std::env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some() {
native_cpu_guard::reject_native_cpu_build("diskgraph Python extension");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# SPDX-License-Identifier: Apache-2.0

from ._c import (
__version__,
BUILD_COMMIT,
export_tt_artifacts,
convert_kumo_config,
ingest,
Expand Down Expand Up @@ -38,6 +40,7 @@
write_success_marker,
touch_success_marker,
verify_snapshot_integrity,
snapshot_range_checksums,
has_resumable_ingest,
validated_index_resume_progress,
compute_col_stats_for_parquet,
Expand Down Expand Up @@ -139,6 +142,7 @@ def __getattr__(name):
"write_success_marker",
"touch_success_marker",
"verify_snapshot_integrity",
"snapshot_range_checksums",
"has_resumable_ingest",
"validated_index_resume_progress",
"compute_col_stats_for_parquet",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,27 @@
def test_public_corruption_error_catches_snapshot_verifier(tmp_path):
with pytest.raises(diskgraph.DiskgraphCorruptionError):
diskgraph.verify_snapshot_integrity(str(tmp_path))


def test_build_identity_is_exported():
assert diskgraph.__version__
assert diskgraph.BUILD_COMMIT


def test_snapshot_range_checksums_match_zlib(tmp_path):
import json
import zlib

payload = bytes(range(256)) * 5
(tmp_path / "a.bin").write_bytes(payload)
(tmp_path / "_MANIFEST.json").write_text(json.dumps({
"schema_version": 1,
"files": [{"rel_path": "a.bin", "size_bytes": len(payload)}],
}))
crcs = diskgraph.snapshot_range_checksums(str(tmp_path), 512)
assert crcs["a.bin"] == [zlib.crc32(payload[i:i + 512])
for i in range(0, len(payload), 512)]
assert crcs["_MANIFEST.json"] == [
zlib.crc32((tmp_path / "_MANIFEST.json").read_bytes())]
with pytest.raises(diskgraph.DiskgraphCorruptionError):
diskgraph.snapshot_range_checksums(str(tmp_path / "missing"), 512)
18 changes: 18 additions & 0 deletions packages/diskgraph/diskgraph-pybind/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,21 @@ fn verify_snapshot_integrity(py: Python<'_>, output_dir: &str) -> PyResult<()> {
.map_err(|e| DiskgraphCorruptionError::new_err(e.to_string()))
}

/// CRC-32 of every `range_bytes` span of each file in the snapshot's
/// `_MANIFEST.json` and of the manifest itself, keyed by relative path.
#[pyfunction]
fn snapshot_range_checksums(
py: Python<'_>,
output_dir: &str,
range_bytes: u64,
) -> PyResult<std::collections::BTreeMap<String, Vec<u32>>> {
let output_dir = std::path::PathBuf::from(output_dir);
py.detach(|| {
diskgraph_ingest::snapshot_mirror::snapshot_range_checksums(&output_dir, range_bytes)
})
.map_err(|e| DiskgraphCorruptionError::new_err(e.to_string()))
}

#[pyfunction]
fn has_resumable_ingest(output_dir: &str) -> PyResult<bool> {
diskgraph_ingest::initial_resume_phase(std::path::Path::new(output_dir))
Expand Down Expand Up @@ -1593,6 +1608,8 @@ fn diskgraph_pybind(m: &Bound<'_, PyModule>) -> PyResult<()> {
"DiskgraphCorruptionError",
m.py().get_type::<DiskgraphCorruptionError>(),
)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add("BUILD_COMMIT", env!("DISKGRAPH_BUILD_COMMIT"))?;
m.add_function(wrap_pyfunction!(export_tt_artifacts, m)?)?;
m.add_function(wrap_pyfunction!(convert_kumo_config, m)?)?;
m.add_function(wrap_pyfunction!(ingest, m)?)?;
Expand All @@ -1606,6 +1623,7 @@ fn diskgraph_pybind(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(success_marker::write_success_marker, m)?)?;
m.add_function(wrap_pyfunction!(success_marker::touch_success_marker, m)?)?;
m.add_function(wrap_pyfunction!(verify_snapshot_integrity, m)?)?;
m.add_function(wrap_pyfunction!(snapshot_range_checksums, m)?)?;
m.add_function(wrap_pyfunction!(has_resumable_ingest, m)?)?;
m.add_function(wrap_pyfunction!(validated_index_resume_progress, m)?)?;
m.add_function(wrap_pyfunction!(compute_col_stats_for_parquet, m)?)?;
Expand Down
9 changes: 8 additions & 1 deletion packages/diskgraph/third_party/artifact-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,13 @@
"source_kind": "registry-or-git",
"version": "0.3.0"
},
{
"license": "MIT OR Apache-2.0",
"name": "crc32fast",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"source_kind": "registry-or-git",
"version": "1.5.0"
},
{
"license": "MIT OR Apache-2.0",
"name": "crossbeam-deque",
Expand Down Expand Up @@ -5202,6 +5209,6 @@
]
}
],
"cargo_lock_sha256": "297851eb08303a13a97f091c68cf3ce5ccac633ad3d7fe09724c1506e486cdaf",
"cargo_lock_sha256": "a1c319a5eb83c1a60f056c213932c12e385f0cff468da4db0021947f11b7aa07",
"schema": 1
}
Loading
Loading