Skip to content

ADR-339: Add Sample/Manifest/ManifestStore data model for ML pipeline - #252

Open
jodavis-claude wants to merge 3 commits into
dev/claude/ADR-338-wire-mypy-strictfrom
dev/claude/ADR-339-sample-manifest-data-model
Open

ADR-339: Add Sample/Manifest/ManifestStore data model for ML pipeline#252
jodavis-claude wants to merge 3 commits into
dev/claude/ADR-338-wire-mypy-strictfrom
dev/claude/ADR-339-sample-manifest-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 sample dataclass hierarchy, typed manifest collection, and JSON round-trip persistence that every later ML pipeline stage (from PhraseVariator through ModelEvaluator) will construct, index, and serialise through.

Changes

  • ml/pipeline/core/sample.py (new) — Sample, SampleWithPath, TextSample, AudioSample, SampleSpectrogram, SampleTokens dataclasses. TextSample.__post_init__ computes content_hash = sha256(content) and sets name = content_hash.
  • ml/pipeline/core/manifest.py (new) — Manifest[S] (generic collection, by_name/by_content_hash lookups, raises ValueError on duplicate names) and ManifestStore (JSON schema-v1 round-trip, type-dispatch serialise/deserialise, raises ValueError on empty/mixed-sample-type writes).
  • ml/pipeline/core/__init__.py (new) — empty, matching the existing ml/pipeline/__init__.py convention.
  • ml/test/pipeline/__init__.py, ml/test/pipeline/core/__init__.py (new) — package inits for the new test tree.
  • ml/test/pipeline/core/test_sample.py (new) — direct unit tests for TextSample.__post_init__.
  • ml/test/pipeline/core/test_manifest.py (new) — unit tests for Manifest[S].
  • ml/test/pipeline/core/test_manifest_store.py (new) — unit tests for ManifestStore.
  • ml/requirements.txt (modified) — added pytest==9.1.1 pin (previously only present transitively in the devcontainer venv, not pinned).
  • ml/pyproject.toml (modified) — added minimal [tool.pytest.ini_options] with testpaths = ["test"].

Design decisions

  • label lives on TextSample/AudioSample directly, not on the shared Sample base — cross-referencing the spec, it's only ever read off those two concrete types.
  • parent_name/parent_content_hash/applied_values live on SampleWithPath rather than a new intermediate base class — every sample type needing lineage tracking is also exactly the set that writes a physical file.
  • SampleSpectrogram/SampleTokens extend SampleWithPath (not Sample directly), consistent with the spec's per-sample physical-file output model for ModifierStage subclasses. This is an inference from the spec, not an explicit statement — worth confirming before ADR-347/ADR-348 build on it.
  • TextSample.name/content_hash are field(init=False, default=""), computed in __post_init__, so callers only ever pass content/label.
  • ManifestStore.read() takes no sample_type parameter — write() records sample_type (class name) and schema_version: 1 in the JSON document; read() dispatches via an internal type registry and raises KeyError on an unrecognized sample_type.
  • ManifestStore is a stateless, no-arg class.
  • Did not wire pytest into any scripts/validate-*.sh — out of scope per the task's exit criteria; left as a known gap.
  • Did not create ml/pipeline/core/_doc_core.md — deferred until ADR-340/ADR-341 complete the core subpackage, per the spec's own doc-timing convention.
  • No E2E/Gherkin scenario added — this repo's only E2E harness is C#/Reqnroll for the WPF app; the spec explicitly defers the first Python E2E test to a later task once enough pipeline stages exist to run end-to-end.

Testing completed

  • All 21 unit tests pass: cd ml && python3 -m pytest
  • mypy --strict pipeline test (run from ml/) reports no issues across all 10 source files
  • Manifest[S]/ManifestStore were driven through genuine red-green-refactor TDD by hand (confirmed each red failure was for the right reason before implementing, ran a refactor pass after each green)

jodavis added 2 commits August 6, 2026 04:38
Sample/SampleWithPath/TextSample/AudioSample/SampleSpectrogram/SampleTokens dataclasses
in ml/pipeline/core/sample.py. TextSample.__post_init__ computes content_hash =
sha256(content) and sets name = content_hash, per the content-addressable identity
decision in ml/_spec_OopPipeline.md. Wrapper-tier per the spec's component taxonomy;
__post_init__ gets direct unit test coverage since it's genuine computed behavior.

Also adds pytest as an explicit ml/requirements.txt dependency and minimal
[tool.pytest.ini_options] config to ml/pyproject.toml -- needed for this task's tests
to be reliably runnable outside this devcontainer's pre-provisioned /opt/ml-env venv.
Manifest[S] (ml/pipeline/core/manifest.py): a typed, in-memory sample collection with
by_name/by_content_hash lookup, indexed at construction. Raises ValueError on a
duplicate sample name -- a load-bearing correctness check per the spec, covered directly.

ManifestStore (same file, per the spec's directory layout): JSON round-trip persistence,
schema_version 1. write() raises ValueError on an empty manifest or mixed sample types.
read() dispatches on a self-describing 'sample_type' field recorded by write(), so callers
don't need to pass an expected type back in -- resolves an open question the task brief
flagged as unspecified in the spec. Covered test-first for both TextSample and AudioSample
round trips, multi-sample round trips, and the unknown-sample-type read failure.

Both are Testable-tier per the spec's component taxonomy.

@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 and ml/_spec_OopPipeline.md. All four exit-criteria checklist items are met: the Sample/SampleWithPath/TextSample/AudioSample/SampleSpectrogram/SampleTokens hierarchy in sample.py (with TextSample.__post_init__ correctly setting name = content_hash), Manifest[S] with by_name/by_content_hash lookups and duplicate-name ValueError, and ManifestStore's JSON schema-v1 round-trip with type-dispatch serialise/deserialise and ValueError on empty/mixed-type writes. Tests were written test-first and land in ml/test/pipeline/core/.

Verified independently: python3 -m pytest (21/21 pass) and mypy --strict pipeline test (clean across all 10 source files) both pass locally, and scripts/validate-ml-build.sh passes.

One non-blocking suggestion posted inline. Two process notes for awareness, not blocking this PR:

  • No quality-gate script currently runs the new ml/test/ pytest suite (scripts/validate-ml-build.sh only runs mypy --strict; scripts/validate-tests.sh only runs dotnet test). This is called out as a known ambiguity in the task brief and isn't owned by any task in the spec's breakdown yet -- worth a follow-up work item so these new tests are actually enforced in CI, not just runnable locally.
  • SampleSpectrogram/SampleTokens extending SampleWithPath (vs. Sample directly) is explicitly flagged in the PR description as an inference from the spec rather than a stated requirement -- worth confirming before ADR-347/ADR-348 build on it, since correcting the hierarchy later would ripple through those tasks.

No Priority 1-4 blocking issues found. Approving.

Comment thread ml/pipeline/core/manifest.py

@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 — ADR-339

Re-reviewed PR #252 after the first-pass review. No commits landed since the prior review pass, so there are no newly modified files to scan for regressions.

Prior thread status:

  • ml/pipeline/core/manifest.py:51 (content-hash collision on by_content_hash, last-one-wins) — the original comment explicitly framed this as a non-blocking suggestion out of scope for ADR-339's exit criteria (which only require duplicate-name checking), deferred to a deliberate decision before ADR-341 starts relying on content-hash lookups. Resolving this thread for ADR-339 sign-off on that basis; the recommendation to make and document a deliberate choice (raise/warn/accept-last-wins) still stands as a follow-up before ADR-341 begins.

Independent verification: python3 -m pytest (21/21 pass), mypy --strict pipeline test (clean), scripts/validate-ml-build.sh all pass. git diff --stat against the base branch matches the PR diff exactly.

No new Priority 1-4 issues found. All four exit-criteria checklist items from ml/_spec_OopPipeline.md's ADR-339 section remain met.

Signing off: approved.

@jodavis-claude
jodavis-claude marked this pull request as ready for review August 6, 2026 05:00
@jodavis-claude
jodavis-claude requested a review from jodavis August 6, 2026 05:03
PRs should not be limited to running build-and-test only when they target main or a feature branch. This is required for using stacked PRs.
@github-actions

Copy link
Copy Markdown

Test Results

401 tests  ±0   401 ✅ ±0   2m 1s ⏱️ -29s
  5 suites ±0     0 💤 ±0 
  5 files   ±0     0 ❌ ±0 

Results for commit 04a7121. ± Comparison against base commit a570da3.

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