8000
Skip to content

feat(datasets): immutable DatasetVersion snapshots and Run provenance (#2701) - #2721

Open
Payal2000 wants to merge 1 commit into
truera:mainfrom
Payal2000:payal/2701-immutable-dataset-versions
Open

feat(datasets): immutable DatasetVersion snapshots and Run provenance (#2701)#2721
Payal2000 wants to merge 1 commit into
truera:mainfrom
Payal2000:payal/2701-immutable-dataset-versions

Conversation

@Payal2000
Copy link
Copy Markdown 8000
Contributor

Closes #2701

Problem

Dataset holds a stable identity while GroundTruth rows point straight at it, so adding or replacing examples changes the effective test set in place. An evaluation cannot be reproduced, and two runs cannot be compared with any confidence that they saw the same examples.

What this adds

Immutable, content-addressed snapshots alongside the existing mutable tables.

  • DatasetVersion / DatasetVersionItem (schema/dataset.py). A version id hashes the dataset, the ordered contents and the source metadata, so publishing identical content is idempotent and returns the existing version rather than duplicating or updating it. description and parent_dataset_version_id are provenance annotations and stay out of the hash.
  • Storage: dataset_version and dataset_version_item tables, alembic revision 13. (dataset_id, version_index) is unique so version history cannot fork, and version_index is the deterministic ordering key for latest-version resolution.
  • SDK: TruSession.create_dataset_version, get_dataset_version, list_dataset_versions, compare_dataset_versions. Publishing accepts a DataFrame with a column_spec, or a GroundTruth sequence.
  • Run provenance: optional dataset_version_id on RunConfig and Run.SourceInfo, persisted into run source info by both the OSS and Snowflake DAOs, read back by run.start(), and surfaced by RunDiff.provenance() — which warns when the two runs read different versions, since the deltas then mix app changes with test-set changes.
  • Docs: a Dataset Versions component guide, plus updated schema and migration tables in contributing/database.md. API reference is generated from docstrings.

Backward compatibility

Existing Dataset and GroundTruth rows are untouched. A dataset that predates versioning is exposed as version zero, reconstructed from its ground truth rows by a compatibility loader that reads but never rewrites the original payloads. Version zero is materialized as a real row on the first publish for that dataset, so it stays loadable afterwards and becomes the parent of the first published version.

The Snowflake source_info payload only carries dataset_version_id when a version is actually pinned, so unversioned runs send an unchanged payload.

Three judgment calls worth reviewing

The issue left these underdetermined. Each is documented in the code; happy to flip any of them.

  1. Item identity excludes meta and splits. The issue asks for ids from "normalized example content" but also that metadata "round-trip without changing item identity unexpectedly". If metadata were part of identity, re-tagging an example would show up in a diff as removed and added. So identity is input + expected_response + expected_contexts + input_id. To stop that from silently dropping annotations, the version hash covers the full per-item payload rather than only the ordered item ids as the issue literally specifies.

  2. get_ground_truth(dataset_name=...) is deliberately unchanged. The issue says name-only calls should resolve to the latest version, but the acceptance criteria also require that current APIs keep working. Resolving to the latest version would mean that publishing a version of a subset silently narrows what existing callers read. Legacy semantics are kept and dataset_version_id is an opt-in parameter; latest-resolution applies to the new version-aware APIs.

  3. A dataset name can map to several rows, because dataset_id hashes name and metadata. Versions hang off the primary row (lowest id) so that reading version zero and materializing it cannot disagree.

Tests

58 unit tests in tests/unit/test_dataset_versions.py covering version creation from DataFrames and GroundTruth sequences, content hashing, idempotency, immutability, parent provenance, split and metadata round trips, version-zero compatibility, latest and exact lookup, membership comparison, and run source provenance.

A persistence-contract helper is wired into tests/integration/test_database.py for sqlite, postgres and mysql, asserting that a version round-trips to the same content hash it was stored under.

Verified locally: the new unit suite plus the existing run/DAO/dashboard tests pass, as does the sqlite persistence contract. Postgres and mysql need the docker/test-database.yaml services and will run in CI. ruff format and ruff check are clean.

Unrelated bug noticed

SQLAlchemyDB.get_datasets() raises AttributeError: 'Dataset' object has no attribute 'name' — it reads ds.name/ds.meta off the ORM row, but those live inside dataset_json. This is broken on main today and is left alone here; this PR uses its own resolution path. Worth a separate fix.

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Aug 21, 2026
Adds content-addressed, immutable snapshots of a dataset's examples so an
evaluation can be reproduced and two runs can be compared with confidence
that they read the same test set.

- `DatasetVersion` / `DatasetVersionItem` schemas. A version id hashes the
  dataset, the ordered contents and the source metadata, so publishing
  identical content is idempotent; `description` and the parent pointer are
  provenance only and stay out of the hash. An item id hashes the normalized
  example content plus the optional caller-supplied input id, so `meta` and
  `splits` can be re-annotated without breaking membership comparison.
- `dataset_version` / `dataset_version_item` tables (alembic revision 13),
  with `(dataset_id, version_index)` unique so version history cannot fork.
- `TruSession.create_dataset_version`, `get_dataset_version`,
  `list_dataset_versions` and `compare_dataset_versions`; publishing accepts a
  DataFrame or a `GroundTruth` sequence.
- Datasets predating versioning are exposed as version zero by a
  compatibility loader that reads, but never rewrites, their ground truth
  payloads, and is materialized on the first publish so it stays loadable.
  `get_ground_truth(dataset_name=...)` is deliberately unchanged; pass
  `dataset_version_id` to read a pinned snapshot.
- Optional `dataset_version_id` on `RunConfig` and `Run.SourceInfo`, persisted
  in run source info, read back by `run.start()`, and reported by
  `RunDiff.provenance()`, which now warns when two runs read different
  versions.

Closes truera#2701
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Immutable DatasetVersion snapshots and Run provenance

1 participant

0