ADR-339: ML pipeline core data model (Sample hierarchy, Manifest, ManifestStore) - #248
Conversation
…Sample, AudioSample, SampleSpectrogram, SampleTokens)
…ps, duplicate-name rejection, len/iter enumeration)
…ed — happy-path write/read round-trip (`TextSample`), `write` raising `ValueError` on empty manifest, and `write` raising `ValueError` on mixed sample types are all exercised, covering every branch in `write`/`read`. Serialise/deserialise is a single generic path (`dataclasses.asdict`/`sample_type(**data)`) with no per-type branching, and was spot-verified (round-trip equality) for `AudioSample`, `SampleSpectrogram`, and `SampleTokens` as well — no further behavioral gap exists to drive with a red test. `mypy --strict pipeline test` passes clean.)
…SON sample_type field instead of requiring the caller to pass it
jodavis-claude
left a comment
There was a problem hiding this comment.
Reviewed against ADR-339's exit criteria (Sample hierarchy, Manifest[S], ManifestStore) and ml/_spec_OopPipeline.md's Key Design Decisions / Component Breakdown. All four exit-criteria bullets are met, mypy --strict pipeline test and python3 -m pytest test/ -v both pass locally (15/15). Tests follow AAA structure with clear Arrange/Act/Assert comments and one behavior per test.
Two correctness/fault-tolerance issues in manifest.py worth addressing before merge (see inline comments): a silent duplicate-content_hash overwrite in Manifest.add, and a raw KeyError (instead of a descriptive ValueError, consistent with the rest of ManifestStore) when read() encounters an unrecognized sample_type.
Note (non-blocking, already flagged by the developer in the PR description): pytest still isn't wired into scripts/validate.sh, so this PR's tests can't be verified by the project's standard quality-gate script yet. Worth a quick follow-up task, but out of scope for ADR-339's own exit criteria.
Previously, adding a sample whose content_hash collided with an existing sample's content_hash (but had a different name) silently overwrote the earlier sample in _by_content_hash, making it permanently unreachable via by_content_hash() with no error. content_hash is the field the spec frames as actually determining sample identity, so an undetected collision here was a real silent-failure risk. Mirrors the existing duplicate-name check. Addresses PR #248 review comment on manifest.py:37.
…estStore.read() Previously an unrecognized sample_type in the JSON (corrupted file, hand-edited manifest, or an unknown future schema version) raised a bare KeyError, inconsistent with the descriptive ValueError used for every other ManifestStore failure mode (empty manifest, mixed types). Addresses PR #248 review comment on manifest.py:74.
jodavis-claude
left a comment
There was a problem hiding this comment.
Sign-off review
Both prior review threads verified as correctly addressed:
Manifest.add()silent content_hash overwrite — fixed with aValueErrorcheck on duplicatecontent_hash(mirroring the existing duplicate-name check), placed before either backing dict is mutated. Regression testtest_add_duplicate_content_hash_different_names_raises_value_errorusesAudioSampleto correctly isolate the content_hash collision from the name-uniqueness path. Verified.ManifestStore.read()bareKeyErroron unknown sample_type — fixed with atry/except KeyErrorthat re-raises a descriptiveValueError, consistent with the class's other failure modes. Regression testtest_read_unrecognized_sample_type_raises_value_errorcovers it. Verified.
Ran the full suite locally: 17/17 pytest tests pass, mypy --strict pipeline test clean.
No new issues found in the files touched by this fix pass (ml/pipeline/core/manifest.py, ml/test/pipeline/core/test_manifest.py, ml/test/pipeline/core/test_manifest_store.py).
One pre-existing note carried forward for awareness, not blocking this PR: Known Ambiguity #1 (no pytest wiring into scripts/validate.sh/ml/requirements.txt) remains unresolved, as flagged in the original task brief and implementation summary — Python unit tests still cannot be verified through the project's standard quality-gate script. Worth a small follow-up task before the ML test suite grows further.
Approving for hand-off.
final-sign-off and work-with-pr reference \$REVIEW_ASSIGNEE_EMAIL to look up the human reviewer's Jira/GitHub identity at hand-off, but it was never set anywhere, so every task's PR hand-off stalled on the reviewer-assignment step (hit on both ADR-338 and ADR-339). Note: GitHub rejects a review request where reviewer == PR author. These PRs are currently authored under the same jodavis account this resolves to, so the GitHub review-request half of final-sign-off's step 2 is expected to no-op/skip for now (Jira assignment still applies) until PR authorship moves to a separate identity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhMgWJeN4ytrcZdaWZYgqY
Work item
ADR-339— build the unifiedSample/Manifest[S]/ManifestStoredata model inml/pipeline/core/: the typed sample hierarchy, generic manifest collection, and JSON (schema v1) round-trip store that every later ML pipeline stage (ADR-340 onward) will serialise through.Changes
ml/pipeline/core/__init__.py— new, empty package marker for thecore/subpackage.ml/pipeline/core/sample.py— new.Samplebase dataclass (name,content_hash,applied_values,parent_name,parent_content_hash, all defaulted);SampleWithPath(Sample)addspath;TextSample(Sample)addscontent/label, with__post_init__settingname = content_hash;AudioSample(SampleWithPath)addstranscript/label/seed;SampleSpectrogram/SampleTokens(bothSampleWithPath, path-based, noseed).ml/pipeline/core/manifest.py— new.Manifest[S](classicGeneric[S]/TypeVarstyle;by_name/by_content_hashdict-backed lookups;add()raisesValueErroron duplicate names;__len__/__iter__for enumeration).ManifestStore(write/read; JSON schema v1 —schema_version,sample_type,samples;writeraisesValueErroron empty manifest or mixed sample types;readtype-dispatches off the persistedsample_typefield via a_SAMPLE_TYPES_BY_NAMEregistry).ml/test/pipeline/__init__.py,ml/test/pipeline/core/__init__.py— new, empty test package markers mirroring the source layout.ml/test/pipeline/core/test_sample.py,test_manifest.py,test_manifest_store.py— new, test-first unit tests covering all of the above.Design decisions
Manifest.__len__/__iter__were added beyond the Component Breakdown's literal text — needed byManifestStore.writeto detect an empty manifest and inspect sample types, judged as withinManifest[S]'s own collection responsibility.ManifestStore.readimplementation wrote asample_typefield but still required the caller to pass the type explicitly rather than dispatching off it. Replaced with a_SAMPLE_TYPES_BY_NAMEregistry so deserialization dispatches purely from the persisted JSON (separate commit).SampleSpectrogram/SampleTokensare path-based (extendSampleWithPath, no extra fields), consistent withAudioSampleand theModifierStageoutput-directory pattern — resolves a known ambiguity per the task brief's own recommendation.pytest) is wired intoscripts/validate.shyet, so this PR's unit tests cannot be verified by the project's standard build/test quality gate script. Recommend a small follow-up task addingpytesttoml/requirements.txtand ascripts/validate-ml-tests.sh/.cmd.Testing completed
python3 -m pytest test/ -vfromml/).mypy --strict pipeline testpasses with zero issues.