feat BREAKING slice-7 feat/slice-7-t3-dataset-review #1 / 1
41 days ago · Jun 11, 2026 · Diff

feat(slice-7): T3 dataset review lifecycle, job queue, provenance retention

- Add dataset_review.py: registered→pending_review→approved|rejected state machine with RejectionReasonCode enum (no free-text reflection) - Add DatasetStore with fixture pre-approval; job creation gate checks DatasetStore.is_approved() and raises DATASET_NOT_APPROVED (HTTP 403) - Add max_concurrent_running to TrainingJobStore (default 1, FIFO bound) - Add queue_state() to store and GET /training/queue to API - Fix expiry path: sweep-triggered deletion retains provenance on tombstone; explicit deleteArtifact still wipes provenance (Slice-5 contract preserved) - Fix fake_worker: provenance validation failure marks job failed, never silently succeeds - Add dataset registration/review/submit/get HTTP routes to api.py - Remove hardcoded ALLOWED_DATASET_IDS contract check; DatasetStore is now the single approval authority (fixture:synthetic-tiny-v1 pre-approved, no regressions) - Add DATASET_NOT_APPROVED to ErrorCode with public safe message - Add 7-tier T3 tests (66 new tests): unit, integration, e2e, stress, data-integrity, performance, security - Update docs/TRAINING-API-CONTRACT.md with T3 section and Slice 9 fixture shapes

All 102 tests pass; provenance self-check, BOM audit, and gitleaks clean.

sha256:fc4c9ad652d1fff3dc508cb6ea02ee710ee6dfc4cb3761291d9900b5e029ea8a sha
+242 ~20 symbols
15 changed · 47 in snapshot files
sha256:b8d1cadd0aced92aab48acf1f74d259bfcbe78992fa258fe780206a7e557c641 snapshot
+242
symbols added
~20
symbols modified
15
files changed
47
files in snapshot
0
dead code introduced
Semantic Changes 262 symbols
~ src/scooling_lab/dataset_review.py .py 32 symbols added
+ DATASET_ID_RE variable variable DATASET_ID_RE L23–23
+ DatasetRecord class class DatasetRecord L116–136
+ to_public_dict method method to_public_dict L125–136
+ DatasetStatus class class DatasetStatus L34–40
+ DatasetStore class class DatasetStore L139–237
+ __init__ method method __init__ L147–159
+ _get_locked method method _get_locked L233–237
+ approve method method approve L192–200
+ get method method get L215–220
+ is_approved method method is_approved L222–231
+ register method method register L161–178
+ reject method method reject L202–213
+ submit_for_review method method submit_for_review L180–190
+ FIXTURE_APPROVED_DATASET_IDS variable variable FIXTURE_APPROVED_DATASET_IDS L29–31
+ FORBIDDEN_STRING_RE variable variable FORBIDDEN_STRING_RE L24–26
+ RejectionReasonCode class class RejectionReasonCode L43–54
+ _DATASET_TRANSITIONS variable variable _DATASET_TRANSITIONS L57–68
+ _utc_now_iso function function _utc_now_iso L107–112
+ dataset_transition function function dataset_transition L71–83
+ ApiError import import ApiError L20–20
+ Enum import import Enum L16–16
+ ErrorCode import import ErrorCode L20–20
+ Mapping import import Mapping L18–18
+ MappingProxyType import import MappingProxyType L17–17
+ annotations import import annotations L11–11
+ dataclass class import dataclass L15–15
+ field import import field L15–15
+ re import import re L13–13
+ threading import import threading L14–14
+ require_dataset_id function function require_dataset_id L86–93
+ require_rejection_reason function function require_rejection_reason L96–104
+ validate_review_request function function validate_review_request L240–268
~ tests/test_t3_data_integrity.py .py 21 symbols added
+ T3DataIntegrityTests class class T3DataIntegrityTests L18–135
+ test_data_integrity_t3_expired_tombstone_carries_no_content_bytes method method test_data_integrity_t3_expired_tombstone_carries_no_content_bytes L41–62
+ test_data_integrity_t3_expired_tombstone_has_no_artifact_list method method test_data_integrity_t3_expired_tombstone_has_no_artifact_list L64–75
+ test_data_integrity_t3_expired_tombstone_provenance_matches_original method method test_data_integrity_t3_expired_tombstone_provenance_matches_original L21–39
+ test_data_integrity_t3_expiry_does_not_affect_other_jobs method method test_data_integrity_t3_expiry_does_not_affect_other_jobs L120–135
+ test_data_integrity_t3_explicit_delete_hash_absence_verified method method test_data_integrity_t3_explicit_delete_hash_absence_verified L77–90
+ test_data_integrity_t3_provenance_schema_version_preserved_through_expiry method method test_data_integrity_t3_provenance_schema_version_preserved_through_expiry L104–118
+ test_data_integrity_t3_rejection_reason_is_enum_not_free_text method method test_data_integrity_t3_rejection_reason_is_enum_not_free_text L92–102
+ ApiError import import ApiError L12–12
+ DatasetStore import import DatasetStore L11–11
+ RejectionReasonCode import import RejectionReasonCode L11–11
+ TrainingApiService import import TrainingApiService L14–14
+ TrainingJobStore import import TrainingJobStore L15–15
+ UTC import import UTC L7–7
+ annotations import import annotations L3–3
+ datetime import import datetime L7–7
+ json import import json L5–5
+ timedelta import import timedelta L7–7
+ unittest import import unittest L6–6
+ valid_payload import import valid_payload L9–9
+ validate_provenance_record import import validate_provenance_record L13–13
~ tests/test_t3_e2e.py .py 27 symbols added
+ T3EndToEndTests class class T3EndToEndTests L21–200
+ _json method method _json L45–57
+ setUp method method setUp L24–34
+ tearDown method method tearDown L36–41
+ test_e2e_t3_expiry_tombstone_provenance_readable_over_http method method test_e2e_t3_expiry_tombstone_provenance_readable_over_http L118–144
+ test_e2e_t3_explicit_delete_wipes_provenance_over_http method method test_e2e_t3_explicit_delete_wipes_provenance_over_http L146–169
+ test_e2e_t3_queue_state_endpoint_returns_counts method method test_e2e_t3_queue_state_endpoint_returns_counts L109–116
+ test_e2e_t3_register_approve_submit_job_over_http method method test_e2e_t3_register_approve_submit_job_over_http L61–88
+ test_e2e_t3_rejection_path_over_http method method test_e2e_t3_rejection_path_over_http L90–107
+ test_e2e_t3_unapproved_dataset_returns_403_over_http method method test_e2e_t3_unapproved_dataset_returns_403_over_http L171–200
+ DatasetStore import import DatasetStore L16–16
+ HTTPError import import HTTPError L10–10
+ RejectionReasonCode import import RejectionReasonCode L16–16
+ Request import import Request L11–11
+ ThreadingHTTPServer import import ThreadingHTTPServer L9–9
+ TrainingApiService import import TrainingApiService L17–17
+ TrainingJobStore import import TrainingJobStore L18–18
+ UTC import import UTC L8–8
+ annotations import import annotations L3–3
+ datetime import import datetime L8–8
+ json import import json L5–5
+ make_handler import import make_handler L15–15
+ threading import import threading L6–6
+ timedelta import import timedelta L8–8
+ unittest import import unittest L7–7
+ urlopen import import urlopen L11–11
+ valid_payload import import valid_payload L13–13
~ tests/test_t3_integration.py .py 30 symbols added
+ T3IntegrationDatasetReviewLifecycleTests class class T3IntegrationDatasetReviewLifecycleTests L34–108
+ test_integration_t3_dataset_status_does_not_expose_free_text method method test_integration_t3_dataset_status_does_not_expose_free_text L99–108
+ test_integration_t3_register_review_approve_then_submit_job method method test_integration_t3_register_review_approve_then_submit_job L37–49
+ test_integration_t3_rejected_dataset_blocks_job method method test_integration_t3_rejected_dataset_blocks_job L68–85
+ test_integration_t3_rejection_reason_in_dataset_record method method test_integration_t3_rejection_reason_in_dataset_record L87–97
+ test_integration_t3_unapproved_dataset_blocks_job_at_api_boundary method method test_integration_t3_unapproved_dataset_blocks_job_at_api_boundary L51–66
+ T3IntegrationProvenanceOnCompletionTests class class T3IntegrationProvenanceOnCompletionTests L140–163
+ test_integration_t3_every_succeeded_job_has_valid_provenance method method test_integration_t3_every_succeeded_job_has_valid_provenance L143–153
+ test_integration_t3_provenance_links_to_artifact method method test_integration_t3_provenance_links_to_artifact L155–163
+ T3IntegrationQueueStateTests class class T3IntegrationQueueStateTests L111–137
+ test_integration_t3_queue_state_after_completion method method test_integration_t3_queue_state_after_completion L129–137
+ test_integration_t3_queue_state_reflects_active_jobs method method test_integration_t3_queue_state_reflects_active_jobs L114–127
+ T3IntegrationRetentionIntegrationTests class class T3IntegrationRetentionIntegrationTests L166–198
+ test_integration_t3_expired_artifact_tombstone_has_provenance method method test_integration_t3_expired_artifact_tombstone_has_provenance L169–186
+ test_integration_t3_explicit_delete_wipes_provenance method method test_integration_t3_explicit_delete_wipes_provenance L188–198
+ _make_service function function _make_service L17–31
+ ApiError import import ApiError L11–11
+ DatasetStatus import import DatasetStatus L10–10
+ DatasetStore import import DatasetStore L10–10
+ ErrorCode import import ErrorCode L11–11
+ RejectionReasonCode import import RejectionReasonCode L10–10
+ TrainingApiService import import TrainingApiService L13–13
+ TrainingJobStore import import TrainingJobStore L14–14
+ UTC import import UTC L6–6
+ annotations import import annotations L3–3
+ datetime import import datetime L6–6
+ timedelta import import timedelta L6–6
+ unittest import import unittest L5–5
+ valid_payload import import valid_payload L8–8
+ validate_provenance_record import import validate_provenance_record L12–12
~ tests/test_t3_performance.py .py 13 symbols added
+ T3PerformanceTests class class T3PerformanceTests L15–76
+ test_performance_t3_bulk_dataset_register_review_is_bounded method method test_performance_t3_bulk_dataset_register_review_is_bounded L45–60
+ test_performance_t3_dataset_store_lookups_are_constant_time method method test_performance_t3_dataset_store_lookups_are_constant_time L18–28
+ test_performance_t3_queue_state_is_fast_under_large_backlog method method test_performance_t3_queue_state_is_fast_under_large_backlog L30–43
+ test_performance_t3_rejected_dataset_lookup_is_bounded method method test_performance_t3_rejected_dataset_lookup_is_bounded L62–76
+ DatasetStore import import DatasetStore L10–10
+ RejectionReasonCode import import RejectionReasonCode L10–10
+ TrainingApiService import import TrainingApiService L11–11
+ TrainingJobStore import import TrainingJobStore L12–12
+ annotations import import annotations L3–3
+ time import import time L5–5
+ unittest import import unittest L6–6
+ valid_payload import import valid_payload L8–8
~ tests/test_t3_security.py .py 20 symbols added
+ T3SecurityDatasetBoundaryTests class class T3SecurityDatasetBoundaryTests L20–169
+ test_security_t3_dataset_not_approved_error_does_not_echo_id method method test_security_t3_dataset_not_approved_error_does_not_echo_id L116–151
+ test_security_t3_injection_shaped_dataset_ids_rejected method method test_security_t3_injection_shaped_dataset_ids_rejected L58–73
+ test_security_t3_injection_shaped_reason_strings_rejected method method test_security_t3_injection_shaped_reason_strings_rejected L75–90
+ test_security_t3_no_free_text_reflected_in_dataset_rejection method method test_security_t3_no_free_text_reflected_in_dataset_rejection L92–101
+ test_security_t3_pending_review_dataset_id_refused method method test_security_t3_pending_review_dataset_id_refused L153–169
+ test_security_t3_rejected_dataset_id_refused_at_job_creation method method test_security_t3_rejected_dataset_id_refused_at_job_creation L39–56
+ test_security_t3_review_request_rejects_unknown_keys method method test_security_t3_review_request_rejects_unknown_keys L103–114
+ test_security_t3_unknown_dataset_id_refused_at_job_creation method method test_security_t3_unknown_dataset_id_refused_at_job_creation L23–37
+ ApiError import import ApiError L15–15
+ DatasetStore import import DatasetStore L9–9
+ ErrorCode import import ErrorCode L15–15
+ RejectionReasonCode import import RejectionReasonCode L9–9
+ TrainingApiService import import TrainingApiService L16–16
+ TrainingJobStore import import TrainingJobStore L17–17
+ annotations import import annotations L3–3
+ require_dataset_id import import require_dataset_id L9–9
+ unittest import import unittest L5–5
+ valid_payload import import valid_payload L7–7
+ validate_review_request import import validate_review_request L9–9
~ tests/test_t3_stress.py .py 16 symbols added
+ T3StressConcurrencyBoundTests class class T3StressConcurrencyBoundTests L33–141
+ test_stress_t3_concurrent_dataset_registrations_are_safe method method test_stress_t3_concurrent_dataset_registrations_are_safe L76–89
+ test_stress_t3_concurrent_review_decisions_are_stable method method test_stress_t3_concurrent_review_decisions_are_stable L91–115
+ test_stress_t3_concurrent_submissions_never_exceed_running_bound method method test_stress_t3_concurrent_submissions_never_exceed_running_bound L36–74
+ test_stress_t3_queue_limit_still_enforced_under_concurrent_load method method test_stress_t3_queue_limit_still_enforced_under_concurrent_load L117–141
+ _make_service function function _make_service L17–30
+ ApiError import import ApiError L12–12
+ DatasetStore import import DatasetStore L11–11
+ ErrorCode import import ErrorCode L12–12
+ TrainingApiService import import TrainingApiService L13–13
+ TrainingJobStore import import TrainingJobStore L14–14
+ annotations import import annotations L3–3
+ futures import import concurrent.futures L5–5
+ threading import import threading L6–6
+ unittest import import unittest L7–7
+ valid_payload import import valid_payload L9–9
~ tests/test_t3_unit.py .py 45 symbols added
+ T3UnitDatasetIdValidationTests class class T3UnitDatasetIdValidationTests L157–184
+ test_unit_t3_path_traversal_and_url_ids_rejected method method test_unit_t3_path_traversal_and_url_ids_rejected L172–184
+ test_unit_t3_safe_dataset_ids_accepted method method test_unit_t3_safe_dataset_ids_accepted L160–170
+ T3UnitDatasetStateMachineTests class class T3UnitDatasetStateMachineTests L23–76
+ test_unit_t3_approved_is_terminal method method test_unit_t3_approved_is_terminal L50–55
+ test_unit_t3_idempotent_same_state_replay method method test_unit_t3_idempotent_same_state_replay L71–76
+ test_unit_t3_pending_to_approved method method test_unit_t3_pending_to_approved L34–40
+ test_unit_t3_pending_to_rejected method method test_unit_t3_pending_to_rejected L42–48
+ test_unit_t3_registered_cannot_skip_to_approved method method test_unit_t3_registered_cannot_skip_to_approved L64–69
+ test_unit_t3_registered_to_pending_review method method test_unit_t3_registered_to_pending_review L26–32
+ test_unit_t3_rejected_is_terminal method method test_unit_t3_rejected_is_terminal L57–62
+ T3UnitDatasetStoreTests class class T3UnitDatasetStoreTests L187–268
+ test_unit_t3_fixture_dataset_pre_approved method method test_unit_t3_fixture_dataset_pre_approved L190–194
+ test_unit_t3_full_approval_lifecycle method method test_unit_t3_full_approval_lifecycle L211–219
+ test_unit_t3_full_rejection_lifecycle method method test_unit_t3_full_rejection_lifecycle L221–230
+ test_unit_t3_queue_state_fields_present method method test_unit_t3_queue_state_fields_present L257–268
+ test_unit_t3_register_already_approved_raises_conflict method method test_unit_t3_register_already_approved_raises_conflict L203–209
+ test_unit_t3_register_new_dataset method method test_unit_t3_register_new_dataset L196–201
+ test_unit_t3_unapproved_dataset_blocks_job_creation method method test_unit_t3_unapproved_dataset_blocks_job_creation L240–255
+ test_unit_t3_unknown_dataset_is_not_found method method test_unit_t3_unknown_dataset_is_not_found L232–238
+ T3UnitRejectionReasonCodeTests class class T3UnitRejectionReasonCodeTests L79–105
+ test_unit_t3_all_reason_codes_are_accepted method method test_unit_t3_all_reason_codes_are_accepted L82–89
+ test_unit_t3_free_text_reason_rejected method method test_unit_t3_free_text_reason_rejected L91–97
+ test_unit_t3_url_shaped_reason_rejected method method test_unit_t3_url_shaped_reason_rejected L99–105
+ T3UnitReviewRequestValidationTests class class T3UnitReviewRequestValidationTests L108–154
+ test_unit_t3_approve_action_accepted method method test_unit_t3_approve_action_accepted L111–116
+ test_unit_t3_approve_with_reason_code_rejected method method test_unit_t3_approve_with_reason_code_rejected L148–154
+ test_unit_t3_reject_action_requires_reason_code method method test_unit_t3_reject_action_requires_reason_code L118–123
+ test_unit_t3_reject_with_valid_reason_accepted method method test_unit_t3_reject_with_valid_reason_accepted L125–132
+ test_unit_t3_unknown_action_rejected method method test_unit_t3_unknown_action_rejected L134–138
+ test_unit_t3_unknown_keys_rejected method method test_unit_t3_unknown_keys_rejected L140–146
+ ApiError import import ApiError L18–18
+ DatasetStatus import import DatasetStatus L10–10
+ DatasetStore import import DatasetStore L10–10
+ ErrorCode import import ErrorCode L18–18
+ RejectionReasonCode import import RejectionReasonCode L10–10
+ TrainingApiService import import TrainingApiService L19–19
+ TrainingJobStatus import import TrainingJobStatus L9–9
+ TrainingJobStore import import TrainingJobStore L20–20
+ annotations import import annotations L3–3
+ dataset_transition import import dataset_transition L10–10
+ require_dataset_id import import require_dataset_id L10–10
+ unittest import import unittest L5–5
+ valid_payload import import valid_payload L7–7
+ validate_review_request import import validate_review_request L10–10
~ docs/TRAINING-API-CONTRACT.md .md 22 symbols added, 1 symbol modified
+ T3: Dataset Review and Job Lifecycle (Slice 7) section T3: Dataset Review and Job Lifecycle (Slice 7) L120–269
+ Dataset Approval Gate section Dataset Approval Gate L191–202
+ Dataset Registration and Review section Dataset Registration and Review L122–174
+ Dataset status response section Dataset status response L161–174
+ code[json] variable variable code[json] L163–171
+ code variable variable code L126–129
+ registerDataset request section registerDataset request L140–149
+ code[json] variable variable code[json] L142–145
+ reviewDataset request section reviewDataset request L149–161
+ code[json] variable variable code[json] L151–155
+ table section table L133–139
+ Job Queue State section Job Queue State L174–191
+ code[json] variable variable code[json] L178–187
+ New Error Code section New Error Code L222–227
+ Provenance Failure Safety section Provenance Failure Safety L216–222
+ Retention Integration — Expiry Tombstone Provenance section Retention Integration — Expiry Tombstone Provenance L202–216
+ Slice 9 Fixture Shapes section Slice 9 Fixture Shapes L227–269
+ code[json]@L232 variable variable code[json]@L232 L232–235
+ code[json]@L237 variable variable code[json]@L237 L237–240
+ code[json]@L242 variable variable code[json]@L242 L242–245
+ code[json]@L252 variable variable code[json]@L252 L252–255
+ code[json]@L257 variable variable code[json]@L257 L257–266
~ src/scooling_lab/api.py .py 2 symbols added, 1 symbol modified
+ DATASET_ID_RE variable variable DATASET_ID_RE L21–21
+ parse_dataset_route function function parse_dataset_route L59–73
~ src/scooling_lab/service.py .py 11 symbols added, 4 symbols modified
+ get_dataset method method get_dataset L87–90
+ get_queue_state method method get_queue_state L94–97
+ register_dataset method method register_dataset L54–61
+ review_dataset method method review_dataset L69–85
+ submit_dataset_for_review method method submit_dataset_for_review L63–67
+ ApiError import import ApiError L20–20
+ DatasetStore import import DatasetStore L15–15
+ ErrorCode import import ErrorCode L20–20
+ RejectionReasonCode import import RejectionReasonCode L15–15
+ threading import import threading L5–5
+ validate_review_request import import validate_review_request L15–15
~ src/scooling_lab/store.py .py 3 symbols added, 8 symbols modified
+ can_run_now method method can_run_now L419–422
+ queue_state method method queue_state L424–440
+ running_count method method running_count L410–417
Files Changed
+8 ~7
47 in snapshot
← Older Oldest on feat/slice-7-t3-dataset-review
All commits
Newer → Latest on feat/slice-7-t3-dataset-review

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:fc4c9ad652d1fff3dc508cb6ea02ee710ee6dfc4cb3761291d9900b5e029ea8a --body "your comment"