Skip to content

ADR-339: ML pipeline core data model (Sample hierarchy, Manifest, ManifestStore) - #248

Open
jodavis-claude wants to merge 7 commits into
dev/claude/ADR-338-mypy-strict-ml-gatefrom
dev/claude/ADR-339-ml-pipeline-core-data-model
Open

ADR-339: ML pipeline core data model (Sample hierarchy, Manifest, ManifestStore)#248
jodavis-claude wants to merge 7 commits into
dev/claude/ADR-338-mypy-strict-ml-gatefrom
dev/claude/ADR-339-ml-pipeline-core-data-model

Conversation

@jodavis-claude

Copy link
Copy Markdown
Collaborator

Work item

ADR-339 — build the unified Sample/Manifest[S]/ManifestStore data model in ml/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 the core/ subpackage.
  • ml/pipeline/core/sample.py — new. Sample base dataclass (name, content_hash, applied_values, parent_name, parent_content_hash, all defaulted); SampleWithPath(Sample) adds path; TextSample(Sample) adds content/label, with __post_init__ setting name = content_hash; AudioSample(SampleWithPath) adds transcript/label/seed; SampleSpectrogram/SampleTokens (both SampleWithPath, path-based, no seed).
  • ml/pipeline/core/manifest.py — new. Manifest[S] (classic Generic[S]/TypeVar style; by_name/by_content_hash dict-backed lookups; add() raises ValueError on duplicate names; __len__/__iter__ for enumeration). ManifestStore (write/read; JSON schema v1 — schema_version, sample_type, samples; write raises ValueError on empty manifest or mixed sample types; read type-dispatches off the persisted sample_type field via a _SAMPLE_TYPES_BY_NAME registry).
  • 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 by ManifestStore.write to detect an empty manifest and inspect sample types, judged as within Manifest[S]'s own collection responsibility.
  • Fixed a gap found during self-review: the TDD trio's first ManifestStore.read implementation wrote a sample_type field but still required the caller to pass the type explicitly rather than dispatching off it. Replaced with a _SAMPLE_TYPES_BY_NAME registry so deserialization dispatches purely from the persisted JSON (separate commit).
  • SampleSpectrogram/SampleTokens are path-based (extend SampleWithPath, no extra fields), consistent with AudioSample and the ModifierStage output-directory pattern — resolves a known ambiguity per the task brief's own recommendation.
  • All six dataclasses are plain (non-frozen), every field defaulted, per the task brief's recommendation (no spec requirement for immutability).
  • Open ambiguity, not resolved in this PR: no Python test runner (pytest) is wired into scripts/validate.sh yet, 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 adding pytest to ml/requirements.txt and a scripts/validate-ml-tests.sh/.cmd.
  • No E2E/Gherkin scenarios — this repo's only E2E harness targets the C# WPF/Blazor UI; not applicable to this Python ML pipeline code.

Testing completed

  • 15 unit tests, all passing (python3 -m pytest test/ -v from ml/).
  • mypy --strict pipeline test passes with zero issues.

jodavis added 4 commits July 27, 2026 23:20
…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 jodavis-claude left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread ml/pipeline/core/manifest.py
Comment thread ml/pipeline/core/manifest.py Outdated
jodavis added 2 commits July 28, 2026 00:35
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 jodavis-claude left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign-off review

Both prior review threads verified as correctly addressed:

  1. Manifest.add() silent content_hash overwrite — fixed with a ValueError check on duplicate content_hash (mirroring the existing duplicate-name check), placed before either backing dict is mutated. Regression test test_add_duplicate_content_hash_different_names_raises_value_error uses AudioSample to correctly isolate the content_hash collision from the name-uniqueness path. Verified.
  2. ManifestStore.read() bare KeyError on unknown sample_type — fixed with a try/except KeyError that re-raises a descriptive ValueError, consistent with the class's other failure modes. Regression test test_read_unrecognized_sample_type_raises_value_error covers 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.

@jodavis-claude
jodavis-claude marked this pull request as ready for review July 28, 2026 00:42
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants