ci: enforce endpoint inventory independently
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
"""
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,19 +85,11 @@ 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(
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user