docs: align training privacy maturity

This commit is contained in:
2026-04-30 22:45:57 -04:00
parent 3d7bf967e3
commit 822fb00a1b
3 changed files with 34 additions and 12 deletions
@@ -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.
+25 -4
View File
@@ -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()
@@ -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: