diff --git a/README.md b/README.md index cc2db74..7bae4c4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Accepted user-facing surfaces: - Training API validation failures are deterministic public validation errors: `400` with `invalid_json_body`, `400` with `missing_required_json_field:`, `400` with `invalid_json_field:`, `400` with `invalid_path_param:`, or `400` with `invalid_limit_query_param:limit`. - Missing Training cases return `404` with `not_found`. -- Unexpected Training API failures return `500` with `internal_server_error`; the public response masks raw internals, and responses do not echo raw internals. +- Masked unexpected Training API failures return `500` with `internal_server_error`; the public response masks raw internals, and responses do not echo raw internals. - Package CLI public errors for invalid input paths and invalid output targets emit one-line JSON with `passed`, `input`, `output`, and `error`; stable public error codes include `input_file_not_found`, `input_path_is_directory`, `output_path_is_directory`, and `output_path_unwritable`. Public API response examples: @@ -85,7 +85,7 @@ Public API response examples: {"error": "not_found"} ``` -- Unexpected Training route failure: any Training route -> HTTP `500` +- Masked internal server error: any Training route -> HTTP `500` ```json {"error": "internal_server_error"} diff --git a/docs/training_case_engine_m1_m6_product_readme.md b/docs/training_case_engine_m1_m6_product_readme.md index 8720a58..9fe0bfc 100644 --- a/docs/training_case_engine_m1_m6_product_readme.md +++ b/docs/training_case_engine_m1_m6_product_readme.md @@ -121,7 +121,7 @@ Training public error catalog: - Invalid path identifiers are deterministic public validation errors and return `400` with `invalid_path_param:`. - Invalid list limits are deterministic public validation errors and return `400` with `invalid_limit_query_param:limit`; valid limits are `1` through `100`. - Missing cases return `404` with `not_found`. -- Unexpected failures return `500` with `internal_server_error`; the public response masks raw internals, and responses do not echo raw internals. +- Masked unexpected failures return `500` with `internal_server_error`; the public response masks raw internals, and responses do not echo raw internals. - Package CLI public errors for invalid input paths and invalid output targets emit one-line JSON with `passed`, `input`, `output`, and `error`; stable public error codes include `input_file_not_found`, `input_path_is_directory`, `output_path_is_directory`, and `output_path_unwritable`. Public API response examples: @@ -162,7 +162,7 @@ Public API response examples: {"error": "not_found"} ``` -- Unexpected Training route failure: any Training route -> HTTP `500` +- Masked internal server error: any Training route -> HTTP `500` ```json {"error": "internal_server_error"} diff --git a/tests/unit/test_documentation_consistency.py b/tests/unit/test_documentation_consistency.py index 5ea31aa..46eb8aa 100644 --- a/tests/unit/test_documentation_consistency.py +++ b/tests/unit/test_documentation_consistency.py @@ -325,7 +325,7 @@ TRAINING_PUBLIC_ERROR_API_EXAMPLES = ( {"error": "not_found"}, ), ( - "Unexpected Training route failure", + "Masked internal server error", "any Training route -> HTTP `500`", {"error": "internal_server_error"}, ), @@ -4756,6 +4756,36 @@ TRAINING_M41_RELEASE_INDEX_REQUIRED_SNIPPET_INVENTORY = ( for forbidden in TRAINING_PUBLIC_ERROR_FORBIDDEN_RAW_WORDING: self.assertNotIn(forbidden.lower(), text) + def test_training_public_error_sections_avoid_sensitive_and_local_internals(self) -> None: + forbidden_terms = ( + "data" + "base", + "data" + "base_url", + "post" + "gresql", + "psy" + "copg", + "pass" + "word", + "pass" + "wd", + "tok" + "en", + "sec" + "ret", + "api" + "_key", + "trace" + "back", + "stack trace", + "runtime" + "error", + ) + local_path_roots = tuple( + "/" + root + "/" + for root in ("tmp", "root", "home", "Users", "var", "etc") + ) + + for path in TRAINING_PUBLIC_ERROR_DOCS: + section = _training_public_error_section(path) + lower_section = section.lower() + with self.subTest(path=path.name): + for forbidden in forbidden_terms: + self.assertNotIn(forbidden.lower(), lower_section) + for local_path_root in local_path_roots: + self.assertNotIn(local_path_root.lower(), lower_section) + self.assertNotRegex(section, r"(? None: text = TRAINING_PRODUCT_DOC.read_text(encoding="utf-8").lower()