diff --git a/docs/training_case_engine_m1_m6_product_readme.md b/docs/training_case_engine_m1_m6_product_readme.md index 06e9b8e..a55c812 100644 --- a/docs/training_case_engine_m1_m6_product_readme.md +++ b/docs/training_case_engine_m1_m6_product_readme.md @@ -1,8 +1,8 @@ -# Training Case Engine M1-M13 Product README +# Training Case Engine M1-M21 Product README -Training Case Engine is the product-facing training module in AI Exchange. M1-M13 provide persisted training cases, a lightweight HTTP API, a dashboard entry, validation hardening, public package import/export, offline package smoke coverage, and product documentation checks. +Training Case Engine is the product-facing training module in AI Exchange. M1-M21 provide persisted training cases, a lightweight HTTP API, a dashboard entry, validation hardening, public package import/export, offline package smoke coverage, product documentation checks, and public response privacy hardening. -Current product maturity through M13 includes API contract coverage, error contract coverage, the fixture/schema contract, and the offline checks registry smoke. +Current product maturity through M21 includes API contract coverage, error contract coverage, the fixture/schema contract, the offline checks registry smoke, recursive public payload sanitization, and public response privacy contract coverage. ## Dashboard Entry @@ -31,7 +31,7 @@ All endpoints use JSON responses. List endpoints accept `limit` with an upper bo | POST | `/training/attempts/{attempt_id}/submit` | Submit an attempt with an optional score. | | GET | `/training/progress` | List progress rows by optional user, skill tag, and limit filters. | -Public case and package responses omit the internal solution key and private answer details. +Public API responses for case list/detail, package export/import, progress, submit, create-attempt, and annotation lifecycle routes are covered by privacy contract tests. Those responses scrub private solution material, including nested internal solution details, before returning JSON. ## Package Smoke @@ -59,12 +59,12 @@ The broader offline check suite also includes this package smoke: - Missing cases return `404` with `not_found`. - Unexpected failures return `500` with `internal_server_error` and do not echo raw internals. -Package import rejects private internal solution-key fields. Package export is stable public JSON and omits private answer detail. +Package import rejects private internal solution fields. Package export is stable public JSON and omits private solution material. ## Data Isolation Guarantees - Training records are scoped to the training domain: cases, tasks, attempts, annotations, scores, and progress. - Training may reference upstream market or rule events as read-only context. - Training APIs and package tooling do not write to signals, executions, backtest results, or live-trading records. -- Public package import/export keeps operator-only notes, rubric internals, and private answer detail out of exported payloads. +- Public package import/export keeps operator-only notes, rubric internals, and private solution material out of exported payloads. - The offline package smoke uses local files and an in-memory training repository; it writes only the requested public export file. diff --git a/tests/unit/test_documentation_consistency.py b/tests/unit/test_documentation_consistency.py index 0722b05..3941b8b 100644 --- a/tests/unit/test_documentation_consistency.py +++ b/tests/unit/test_documentation_consistency.py @@ -72,7 +72,7 @@ class DocumentationConsistencyTests(unittest.TestCase): self.assertEqual(missing, []) - def test_training_case_product_doc_lists_public_m1_m6_surface(self) -> None: + def test_training_case_product_doc_lists_public_m1_m21_surface(self) -> None: text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") for snippet in ( @@ -87,25 +87,46 @@ class DocumentationConsistencyTests(unittest.TestCase): "/training/progress", ".venv/bin/python scripts/run_training_case_package.py", ".venv/bin/python scripts/run_offline_checks.py", - "internal solution key", + "private solution material", "Data Isolation Guarantees", ): with self.subTest(snippet=snippet): self.assertIn(snippet, text) - def test_training_case_product_doc_reflects_current_m13_maturity(self) -> None: + def test_training_case_product_doc_reflects_current_m21_maturity(self) -> None: text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") for snippet in ( - "M1-M13", + "M1-M21", "API contract coverage", "error contract coverage", "fixture/schema contract", "offline checks registry smoke", + "public response privacy contract coverage", ): with self.subTest(snippet=snippet): self.assertIn(snippet, text) + def test_training_case_product_doc_names_public_response_privacy_contracts(self) -> None: + text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8") + + for snippet in ( + "case list/detail", + "package export/import", + "progress", + "submit", + "create-attempt", + "annotation lifecycle", + "privacy contract tests", + "scrub private solution material", + "internal solution details", + ): + with self.subTest(snippet=snippet): + self.assertIn(snippet, text) + + self.assertNotIn("private " + "answer", text.lower()) + self.assertNotIn("answer " + "detail", text.lower()) + def test_training_case_product_doc_api_routes_match_server_route_request(self) -> None: documented_routes = _training_api_routes_from_product_doc() server_routes = _supported_training_routes_from_route_request() diff --git a/tests/unit/test_training_case_docs_cli_m10.py b/tests/unit/test_training_case_docs_cli_m10.py index 8b5e03e..84bf43b 100644 --- a/tests/unit/test_training_case_docs_cli_m10.py +++ b/tests/unit/test_training_case_docs_cli_m10.py @@ -22,6 +22,7 @@ FIXTURE = ROOT / "tests" / "fixtures" / "training_case_package_m6.json" FIXTURE_REF = "tests/fixtures/training_case_package_m6.json" OFFLINE_CHECK_COMMAND = ".venv/bin/python scripts/run_offline_checks.py" PUBLIC_FIELD = "answer" + "_key" +PRIVATE_MATERIAL_PHRASES = ("private solution material", "private " + "answer") RESTRICTED_PUBLIC_TEXT = ( "data" + "base", "data" + "base_url", @@ -189,7 +190,7 @@ class TrainingCaseDocsCliM10Tests(unittest.TestCase): self.assertEqual(fixture_refs, {FIXTURE_REF}) self.assertIn(".venv/bin/python scripts/run_training_case_package.py", text) self.assertIn("public export", lower_text) - self.assertIn("private answer", lower_text) + self.assertTrue(any(phrase in lower_text for phrase in PRIVATE_MATERIAL_PHRASES)) self.assertNotIn(PUBLIC_FIELD, text) def test_example_command_uses_supported_cli_flags_and_fixture(self) -> None: @@ -248,7 +249,7 @@ class TrainingCaseDocsCliM10Tests(unittest.TestCase): self.assertIn(FIXTURE_REF, text) self.assertIn("offline package smoke", lower_text) self.assertIn("public export", lower_text) - self.assertIn("private answer", lower_text) + self.assertTrue(any(phrase in lower_text for phrase in PRIVATE_MATERIAL_PHRASES)) self.assertNotIn(PUBLIC_FIELD, text) def test_offline_checks_registry_runs_training_package_smoke_entry(self) -> None: