From 62501d399a39502fcdd376697e168d072cc0c178 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Mon, 3 Aug 2026 20:29:14 +0200 Subject: [PATCH] ci: enforce endpoint inventory independently --- .gitea/workflows/module-matrix.yml | 2 +- docs/PLATFORM_CONTROL_PLANE.md | 15 +++--- tests/test_platform_interface_inventory.py | 28 +++++++++++ .../endpoint-surface-declarations.json | 7 +++ .../inventory/platform-interface-inventory.py | 50 +++++++++++++------ 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/.gitea/workflows/module-matrix.yml b/.gitea/workflows/module-matrix.yml index 61f8ef4..da50128 100644 --- a/.gitea/workflows/module-matrix.yml +++ b/.gitea/workflows/module-matrix.yml @@ -57,7 +57,7 @@ jobs: run: bash tools/release/install-webui-release-dependencies.sh ../govoplan-core/webui - name: Validate platform endpoint surface declarations working-directory: govoplan - run: .venv/bin/python tools/inventory/platform-interface-inventory.py --strict + run: .venv/bin/python tools/inventory/platform-interface-inventory.py --strict-endpoints - name: Validate Search against PostgreSQL working-directory: govoplan env: diff --git a/docs/PLATFORM_CONTROL_PLANE.md b/docs/PLATFORM_CONTROL_PLANE.md index 668dde7..ea0509d 100644 --- a/docs/PLATFORM_CONTROL_PLANE.md +++ b/docs/PLATFORM_CONTROL_PLANE.md @@ -45,9 +45,11 @@ The command writes: - `audit-reports/platform-inventory/platform-interface-inventory.json` - `audit-reports/platform-inventory/platform-interface-inventory.md` -Use `--strict` in CI. In addition to translation coverage, strict mode requires -every backend endpoint without a statically visible WebUI path to have an exact -entry in +Use `--strict` for the combined translation and endpoint audit. Use +`--strict-endpoints` in the endpoint-surface CI gate so unrelated translation +catalog work cannot disable route classification enforcement. Both strict modes +require every backend endpoint without a statically visible WebUI path to have +an exact entry in `tools/inventory/endpoint-surface-declarations.json`. The registry is keyed by repository, HTTP method, and canonical version-independent path. It accepts: @@ -80,9 +82,10 @@ A backend route without a static frontend reference is also a review candidate: public APIs, workers, callbacks, health checks, connectors, and dynamic URL assembly are valid explanations. -`--strict` currently enforces only translation-catalog completeness. Endpoint -and help classifications need narrow reviewed baselines before they can become -release gates. +The module matrix enforces endpoint declarations with `--strict-endpoints`. +Combined `--strict` additionally fails when used translation keys are absent +from generated locale catalogs. Help-text findings remain review candidates +rather than a release gate because dynamic parent components can supply help. ## Admin Information Architecture diff --git a/tests/test_platform_interface_inventory.py b/tests/test_platform_interface_inventory.py index dadfcc7..269f313 100644 --- a/tests/test_platform_interface_inventory.py +++ b/tests/test_platform_interface_inventory.py @@ -141,6 +141,34 @@ class PlatformInterfaceInventoryTests(unittest.TestCase): self.assertEqual(1, result["summary"]["stale_endpoint_declarations"]) self.assertIsNone(result["api"]["backend_endpoints"][0]["surface"]) + def test_endpoint_only_strict_mode_does_not_fail_on_translation_debt( + self, + ) -> None: + result = { + "translation_health": {"missing_catalog_entries": ["missing.key"]}, + "api": { + "unclassified_endpoints": [], + "stale_endpoint_declarations": [], + }, + } + + self.assertEqual( + [], + inventory._strict_failures( + result, + check_translations=False, + check_endpoints=True, + ), + ) + self.assertEqual( + ["used translation keys are missing from generated catalogs"], + inventory._strict_failures( + result, + check_translations=True, + check_endpoints=True, + ), + ) + def test_fastapi_route_scanner_includes_router_prefix(self) -> None: tree = ast.parse( """ diff --git a/tools/inventory/endpoint-surface-declarations.json b/tools/inventory/endpoint-surface-declarations.json index 2fcaaed..c96757d 100644 --- a/tools/inventory/endpoint-surface-declarations.json +++ b/tools/inventory/endpoint-surface-declarations.json @@ -389,6 +389,13 @@ "rationale": "Campaign delivery diagnostic/status API is retained for bounded support and automation consumers.", "repository": "govoplan-campaign" }, + { + "category": "worker_internal", + "method": "POST", + "path": "/campaigns/operations/artifacts/reconcile", + "rationale": "Privileged, bounded artifact recovery operation used by operators and recovery automation rather than an end-user surface.", + "repository": "govoplan-campaign" + }, { "category": "ui_reachable", "method": "PUT", diff --git a/tools/inventory/platform-interface-inventory.py b/tools/inventory/platform-interface-inventory.py index 51ef334..e47cc01 100644 --- a/tools/inventory/platform-interface-inventory.py +++ b/tools/inventory/platform-interface-inventory.py @@ -45,6 +45,11 @@ def main() -> int: action="store_true", help="Fail on missing translations or incomplete endpoint-surface declarations.", ) + parser.add_argument( + "--strict-endpoints", + action="store_true", + help="Fail only on incomplete or stale endpoint-surface declarations.", + ) parser.add_argument( "--endpoint-declarations", type=Path, @@ -80,20 +85,12 @@ def main() -> int: print(f"Platform inventory JSON: {json_path}") print(f"Platform inventory summary: {markdown_path}") - if args.strict: - failures: list[str] = [] - if inventory["translation_health"]["missing_catalog_entries"]: - failures.append("used translation keys are missing from generated catalogs") - if inventory["api"]["unclassified_endpoints"]: - failures.append( - f"{len(inventory['api']['unclassified_endpoints'])} backend " - "endpoints have no WebUI evidence or surface declaration" - ) - if inventory["api"]["stale_endpoint_declarations"]: - failures.append( - f"{len(inventory['api']['stale_endpoint_declarations'])} " - "endpoint declarations do not match a backend endpoint" - ) + if args.strict or args.strict_endpoints: + failures = _strict_failures( + inventory, + check_translations=args.strict, + check_endpoints=True, + ) if failures: print( "Strict platform inventory failed: " + "; ".join(failures) + ".", @@ -103,6 +100,31 @@ def main() -> int: return 0 +def _strict_failures( + inventory: dict[str, Any], + *, + check_translations: bool, + check_endpoints: bool, +) -> list[str]: + failures: list[str] = [] + if ( + check_translations + and inventory["translation_health"]["missing_catalog_entries"] + ): + failures.append("used translation keys are missing from generated catalogs") + if check_endpoints and inventory["api"]["unclassified_endpoints"]: + failures.append( + f"{len(inventory['api']['unclassified_endpoints'])} backend " + "endpoints have no WebUI evidence or surface declaration" + ) + if check_endpoints and inventory["api"]["stale_endpoint_declarations"]: + failures.append( + f"{len(inventory['api']['stale_endpoint_declarations'])} " + "endpoint declarations do not match a backend endpoint" + ) + return failures + + def _resolve_workspace_root(catalog: dict[str, Any]) -> Path: sibling_root = META_ROOT.parent.resolve() configured_root = Path(str(catalog["default_parent"])).expanduser().resolve()