From 50e9607e72eb9e692a064f9d7306b98682c4d6f8 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Sun, 2 Aug 2026 05:30:20 +0200 Subject: [PATCH] feat: enforce backend endpoint classification --- .gitea/workflows/module-matrix.yml | 3 + docs/PLATFORM_CONTROL_PLANE.md | 22 + tests/test_platform_interface_inventory.py | 88 + .../endpoint-surface-declarations.json | 1591 +++++++++++++++++ .../inventory/platform-interface-inventory.py | 224 ++- 5 files changed, 1899 insertions(+), 29 deletions(-) create mode 100644 tools/inventory/endpoint-surface-declarations.json diff --git a/.gitea/workflows/module-matrix.yml b/.gitea/workflows/module-matrix.yml index 74381dc..7b44e1e 100644 --- a/.gitea/workflows/module-matrix.yml +++ b/.gitea/workflows/module-matrix.yml @@ -46,6 +46,9 @@ jobs: - name: Install WebUI release dependencies with test scripts working-directory: govoplan 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 - name: Validate Search against PostgreSQL working-directory: govoplan env: diff --git a/docs/PLATFORM_CONTROL_PLANE.md b/docs/PLATFORM_CONTROL_PLANE.md index 5afff27..668dde7 100644 --- a/docs/PLATFORM_CONTROL_PLANE.md +++ b/docs/PLATFORM_CONTROL_PLANE.md @@ -45,6 +45,28 @@ 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 +`tools/inventory/endpoint-surface-declarations.json`. The registry is keyed by +repository, HTTP method, and canonical version-independent path. It accepts: + +- `ui_reachable`: a mounted router, generic action, or provider path hides the + reference from static extraction; +- `intentionally_headless`: a capability/API is deliberately consumed without + its own UI; +- `public_integration`: a documented public or interoperability endpoint; +- `worker_internal`: a worker, scheduler, reconciliation, or monitoring path; +- `compatibility`: a retained transition endpoint with a current replacement; +- `missing_ui`: a real UI gap, which must include a Gitea tracking issue; +- `removable`: a reviewed dead endpoint pending removal. + +Strict mode also rejects declarations that no longer match source. When an +endpoint is added, changed, or removed, update its declaration in the same +change. Do not classify an endpoint from a string mismatch alone: first check +mounted prefixes, dynamic action paths, public clients, worker use, and +capability consumers. + It combines: 1. loaded module manifests diff --git a/tests/test_platform_interface_inventory.py b/tests/test_platform_interface_inventory.py index a498573..198a27f 100644 --- a/tests/test_platform_interface_inventory.py +++ b/tests/test_platform_interface_inventory.py @@ -2,7 +2,9 @@ from __future__ import annotations import ast import importlib.util +import json from pathlib import Path +import tempfile import unittest @@ -30,6 +32,92 @@ class PlatformInterfaceInventoryTests(unittest.TestCase): inventory.canonical_api_path("/api/v2/campaigns/{campaign_id}"), "/campaigns/{}", ) + self.assertEqual( + inventory.canonical_api_path("/api/v1/calendar/events/delta${querySuffix}"), + "/calendar/events/delta", + ) + self.assertEqual( + inventory.canonical_api_path("/api/v1/calendar/events/${eventId}"), + "/calendar/events/{}", + ) + + def test_endpoint_declarations_are_exact_and_require_missing_ui_issue(self) -> None: + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "endpoints.json" + path.write_text( + json.dumps( + { + "schema_version": 1, + "endpoints": [ + { + "repository": "govoplan-example", + "method": "GET", + "path": "/example/items/{}", + "category": "public_integration", + "rationale": "Published integration API.", + } + ], + } + ), + encoding="utf-8", + ) + + declarations = inventory._load_endpoint_declarations(path) + + self.assertIn( + ("govoplan-example", "GET", "/example/items/{}"), + declarations, + ) + payload = json.loads(path.read_text(encoding="utf-8")) + payload["endpoints"][0]["category"] = "missing_ui" + path.write_text(json.dumps(payload), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "tracking_issue"): + inventory._load_endpoint_declarations(path) + + def test_inventory_reports_unclassified_and_stale_endpoint_declarations( + self, + ) -> None: + webui = { + "frontendApiReferences": [], + "translationUsages": [], + "translationCatalog": {"en": {}, "de": {}}, + "fields": [], + "labels": [], + "visibleText": [], + "routes": [], + "navigation": [], + "uiCapabilities": [], + "dynamicTranslationUsages": [], + } + endpoint = { + "repository": "govoplan-example", + "method": "GET", + "path": "/api/v1/example/items", + "file": "src/example.py", + "line": 1, + "handler": "items", + "router": "router", + } + stale = { + "repository": "govoplan-example", + "method": "GET", + "path": "/example/removed", + "category": "removable", + "rationale": "Removal is pending.", + } + + result = inventory._assemble_inventory( + webui=webui, + backend_endpoints=[endpoint], + manifests=[], + endpoint_declarations={ + ("govoplan-example", "GET", "/example/removed"): stale, + }, + ) + + self.assertEqual(1, result["summary"]["unclassified_backend_endpoints"]) + self.assertEqual(1, result["summary"]["stale_endpoint_declarations"]) + self.assertIsNone(result["api"]["backend_endpoints"][0]["surface"]) 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 new file mode 100644 index 0000000..3da396c --- /dev/null +++ b/tools/inventory/endpoint-surface-declarations.json @@ -0,0 +1,1591 @@ +{ + "endpoints": [ + { + "category": "intentionally_headless", + "method": "GET", + "path": "/admin/configuration-safety", + "rationale": "Configuration safety is consumed by installation and governance preflight rather than a direct feature page.", + "repository": "govoplan-access" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/admin/configuration-safety/plan", + "rationale": "Configuration safety is consumed by installation and governance preflight rather than a direct feature page.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/admin/function-assignments", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/admin/function-assignments", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "DELETE", + "path": "/admin/function-assignments/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "PATCH", + "path": "/admin/function-assignments/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/admin/function-delegations", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/admin/function-delegations", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "DELETE", + "path": "/admin/function-delegations/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "PATCH", + "path": "/admin/function-delegations/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/admin/functions", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/admin/functions", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "DELETE", + "path": "/admin/functions/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "PATCH", + "path": "/admin/functions/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/admin/identities", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/admin/identities", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "DELETE", + "path": "/admin/identities/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "PATCH", + "path": "/admin/identities/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/admin/organization-units", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/admin/organization-units", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "DELETE", + "path": "/admin/organization-units/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "compatibility", + "method": "PATCH", + "path": "/admin/organization-units/{}", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-access" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/admin/service-accounts", + "rationale": "Service-account lifecycle is implemented but its administration UI is tracked separately.", + "repository": "govoplan-access", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/18" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/admin/service-accounts", + "rationale": "Service-account lifecycle is implemented but its administration UI is tracked separately.", + "repository": "govoplan-access", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/18" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/admin/service-accounts/{}", + "rationale": "Service-account lifecycle is implemented but its administration UI is tracked separately.", + "repository": "govoplan-access", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/18" + }, + { + "category": "missing_ui", + "method": "PATCH", + "path": "/admin/service-accounts/{}", + "rationale": "Service-account lifecycle is implemented but its administration UI is tracked separately.", + "repository": "govoplan-access", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/18" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/admin/service-accounts/{}/retire", + "rationale": "Service-account lifecycle is implemented but its administration UI is tracked separately.", + "repository": "govoplan-access", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/18" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/addresses/address-books/{}/sync-sources", + "rationale": "Address lookup and generic sync lifecycle endpoints are consumed through module/provider workflows.", + "repository": "govoplan-addresses" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/addresses/lookup", + "rationale": "Address lookup and generic sync lifecycle endpoints are consumed through module/provider workflows.", + "repository": "govoplan-addresses" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/addresses/sync-sources/{}/attempts/finish", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-addresses" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/addresses/sync-sources/{}/attempts/start", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-addresses" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/admin/system/modules/install-requests/{}", + "rationale": "Install request/run detail is an operator API; the queue UI uses bounded list and action projections.", + "repository": "govoplan-admin" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/admin/system/modules/install-runs/{}", + "rationale": "Install request/run detail is an operator API; the queue UI uses bounded list and action projections.", + "repository": "govoplan-admin" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/approvals/templates", + "rationale": "Approval-template and escalation administration UI is tracked separately.", + "repository": "govoplan-approvals", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-approvals/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/approvals/templates", + "rationale": "Approval-template and escalation administration UI is tracked separately.", + "repository": "govoplan-approvals", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-approvals/issues/2" + }, + { + "category": "missing_ui", + "method": "PUT", + "path": "/approvals/templates/{}", + "rationale": "Approval-template and escalation administration UI is tracked separately.", + "repository": "govoplan-approvals", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-approvals/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/approvals/templates/{}/publish", + "rationale": "Approval-template and escalation administration UI is tracked separately.", + "repository": "govoplan-approvals", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-approvals/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/approvals/{}/escalate", + "rationale": "Approval-template and escalation administration UI is tracked separately.", + "repository": "govoplan-approvals", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-approvals/issues/2" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/admin/audit/event-deliveries/{}/{}/replay", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-audit" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/admin/audit/event-delivery/metrics", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-audit" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/audit", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-audit" + }, + { + "category": "public_integration", + "method": "POST", + "path": "/calendar/availability/freebusy", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-calendar" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/calendar/caldav/outbox", + "rationale": "CalDAV outbox diagnostics and recovery UI is tracked separately.", + "repository": "govoplan-calendar", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-calendar/issues/21" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/calendar/caldav/outbox/dispatch", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-calendar" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/calendar/caldav/outbox/{}/discard", + "rationale": "CalDAV outbox diagnostics and recovery UI is tracked separately.", + "repository": "govoplan-calendar", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-calendar/issues/21" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/calendar/caldav/outbox/{}/reconcile", + "rationale": "CalDAV outbox diagnostics and recovery UI is tracked separately.", + "repository": "govoplan-calendar", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-calendar/issues/21" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/calendar/caldav/outbox/{}/retry", + "rationale": "CalDAV outbox diagnostics and recovery UI is tracked separately.", + "repository": "govoplan-calendar", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-calendar/issues/21" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/calendar/caldav/sync-due", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-calendar" + }, + { + "category": "public_integration", + "method": "POST", + "path": "/calendar/events/import-ics", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-calendar" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/calendar/events/{}/ics", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-calendar" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/calendar/sync-sources/sync-due", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-calendar" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/campaigns/{}/archive", + "rationale": "Campaign archive lifecycle UI remains tracked by the archive policy issue.", + "repository": "govoplan-campaign", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-campaign/issues/26" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/campaigns/{}/jobs/{}/diagnostics", + "rationale": "Campaign delivery diagnostic/status API is retained for bounded support and automation consumers.", + "repository": "govoplan-campaign" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/campaigns/{}/report/email/{}", + "rationale": "Campaign delivery diagnostic/status API is retained for bounded support and automation consumers.", + "repository": "govoplan-campaign" + }, + { + "category": "ui_reachable", + "method": "PUT", + "path": "/cases/catalog/statuses/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-cases" + }, + { + "category": "ui_reachable", + "method": "PUT", + "path": "/cases/catalog/types/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-cases" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/committee/decision-projections/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-committee" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/committee/workspace/{}/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-committee" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/connectors/feeds/import", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/connectors/feeds/preview", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/connectors/feeds/render", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/connectors/sanctions/runs", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/connectors/sanctions/snapshots", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/connectors/sanctions/sources", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/connectors/sanctions/sources/{}/refresh", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/connectors/tabular-sources", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/connectors/tabular-sources/snapshots", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "DELETE", + "path": "/connectors/tabular-sources/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/connectors/tabular-sources/{}/preview", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-connectors" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/health", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-core" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/health/details", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-core" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/health/ready", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-core" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/ownership/transfers/recoveries", + "rationale": "Administrative recovery starts through a high-assurance API; ordinary transfer decisions use the shared UI.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/ownership/transfers/{}", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/ownership/transfers/{}/acceptance", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/ownership/transfers/{}/cancel", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/ownership/transfers/{}/decline", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/ownership/transfers/{}/owner-approval", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/ownership/transfers/{}/recovery-approval", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/ownership/transfers/{}/recovery-execution", + "rationale": "The shared ownership UI uses a dynamic transfer-action path that the static string scan cannot resolve.", + "repository": "govoplan-core" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/platform/navigation", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-core" + }, + { + "category": "worker_internal", + "method": "GET", + "path": "/dataflow/runs/metrics", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-dataflow" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/dataflow/runs/{}", + "rationale": "Dataflow run and trigger-delivery views resolve this route through dynamic identifiers.", + "repository": "govoplan-dataflow" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/dataflow/triggers/dispatch", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-dataflow" + }, + { + "category": "public_integration", + "method": "POST", + "path": "/dataflow/triggers/events", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-dataflow" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/dataflow/triggers/{}/deliveries", + "rationale": "Dataflow run and trigger-delivery views resolve this route through dynamic identifiers.", + "repository": "govoplan-dataflow" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/decisions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-decisions" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/decisions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-decisions" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/decisions/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-decisions" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/dist-lists/snapshots/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-dist-lists" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/dist-lists/write-decision", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-dist-lists" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/docs/sources", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-docs" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/encryption/disable-preflight", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/envelopes", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/encryption/envelopes/{}", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/migrations", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/migrations/{}/outcome", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/recoveries", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/recoveries/{}/decision", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/vaults", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/encryption/vaults/{}", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/vaults/{}/destruction", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/vaults/{}/reconcile", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/vaults/{}/revoke", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/encryption/vaults/{}/rotate", + "rationale": "Encryption custodian, lifecycle, and recovery administration UI is tracked separately.", + "repository": "govoplan-encryption", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-encryption/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/files/integrity/findings/{}/cleanup", + "rationale": "File-integrity operations UI is tracked separately.", + "repository": "govoplan-files", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/40" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/files/integrity/findings/{}/recheck", + "rationale": "File-integrity operations UI is tracked separately.", + "repository": "govoplan-files", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/40" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/files/integrity/scans", + "rationale": "File-integrity operations UI is tracked separately.", + "repository": "govoplan-files", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/40" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/files/integrity/scans", + "rationale": "File-integrity operations UI is tracked separately.", + "repository": "govoplan-files", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/40" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/files/integrity/scans/{}/findings", + "rationale": "File-integrity operations UI is tracked separately.", + "repository": "govoplan-files", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/40" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/files/integrity/scans/{}/run", + "rationale": "File-integrity operations UI is tracked separately.", + "repository": "govoplan-files", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/40" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/files/upload-zip", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-files" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/forms/definitions/{}/diagnostics", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-forms" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/forms/definitions/{}/history", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-forms" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/forms-runtime/instances/{}/handoffs/{}/reconcile", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-forms-runtime" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/forms-runtime/instances/{}/handoffs/{}/retry", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-forms-runtime" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/forms-runtime/instances/{}/transition", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-forms-runtime" + }, + { + "category": "compatibility", + "method": "GET", + "path": "/identity/identities", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-identity" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/identity-trust/assurance/check", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/identity-trust/assurance/evidence", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/identity-trust/device-keys", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/identity-trust/device-keys", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/identity-trust/device-keys/{}/revoke", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/identity-trust/epochs/rotate", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/identity-trust/key-access/decide", + "rationale": "Identity-assurance and device-key administration UI is tracked separately.", + "repository": "govoplan-identity-trust", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-identity-trust/issues/2" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/function-assignment-changes", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-idm" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/function-assignment-changes", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-idm" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/function-assignment-changes/capability", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-idm" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/function-assignment-changes/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-idm" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/function-assignment-changes/{}/actions", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-idm" + }, + { + "category": "worker_internal", + "method": "GET", + "path": "/mail/delivery-commands/{}", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-mail" + }, + { + "category": "worker_internal", + "method": "GET", + "path": "/mail/delivery-commands/{}/diagnostics", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-mail" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/mail/delivery-commands/{}/reconcile", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-mail" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/mail/delivery-commands/{}/resend", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-mail" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/mandates/definitions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-mandates" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/mandates/definitions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-mandates" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/mandates/definitions/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-mandates" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/mandates/resolve", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-mandates" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/notifications/{}/deliver", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-notifications" + }, + { + "category": "worker_internal", + "method": "GET", + "path": "/ops/readiness", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-ops" + }, + { + "category": "worker_internal", + "method": "GET", + "path": "/ops/runtime/nodes", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-ops" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/organizations/model-templates", + "rationale": "Template comparison, publication, and explicit tenant upgrade UI is tracked separately.", + "repository": "govoplan-organizations", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-organizations/issues/6" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/organizations/model-templates", + "rationale": "Template comparison, publication, and explicit tenant upgrade UI is tracked separately.", + "repository": "govoplan-organizations", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-organizations/issues/6" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/organizations/model-templates/{}/versions", + "rationale": "Template comparison, publication, and explicit tenant upgrade UI is tracked separately.", + "repository": "govoplan-organizations", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-organizations/issues/6" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/organizations/model-templates/{}/versions/{}/instantiate", + "rationale": "Template comparison, publication, and explicit tenant upgrade UI is tracked separately.", + "repository": "govoplan-organizations", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-organizations/issues/6" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/organizations/model-templates/{}/versions/{}/publish", + "rationale": "Template comparison, publication, and explicit tenant upgrade UI is tracked separately.", + "repository": "govoplan-organizations", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-organizations/issues/6" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/parties", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-parties" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/parties/resolve", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-parties" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/parties/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-parties" + }, + { + "category": "intentionally_headless", + "method": "DELETE", + "path": "/admin/definition-policies/{}/{}", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/admin/definition-policies/{}/{}", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "PUT", + "path": "/admin/definition-policies/{}/{}", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/admin/privacy-retention/policies/{}/simulate", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "DELETE", + "path": "/admin/view-policies/{}", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/admin/view-policies/{}", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "PUT", + "path": "/admin/view-policies/{}", + "rationale": "Policy definition and simulation APIs are embedded through governed module settings and capability consumers.", + "repository": "govoplan-policy" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/poll/polls", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/poll/polls/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "PATCH", + "path": "/poll/polls/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/archive", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/close", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/decide", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/draft", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/poll/polls/{}/invitations", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/invitations", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "DELETE", + "path": "/poll/polls/{}/invitations/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/open", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/poll/polls/{}/responses", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/responses", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/poll/polls/{}/summary", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/poll/polls/{}/transitions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/transitions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/poll/polls/{}/unarchive", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-poll" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/poll/public/{}", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-poll" + }, + { + "category": "public_integration", + "method": "POST", + "path": "/poll/public/{}/responses", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-poll" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/postbox/deliveries", + "rationale": "Postbox delivery, access-decision, and routing-preview APIs are consumed by Campaign and other module capabilities.", + "repository": "govoplan-postbox" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/postbox/directory/{}/access", + "rationale": "Postbox delivery, access-decision, and routing-preview APIs are consumed by Campaign and other module capabilities.", + "repository": "govoplan-postbox" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/postbox/routing/dry-run", + "rationale": "Postbox delivery, access-decision, and routing-preview APIs are consumed by Campaign and other module capabilities.", + "repository": "govoplan-postbox" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/projects/objects/{}/{}/events", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-projects" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/projects/objects/{}/{}/history", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-projects" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/reporting/definitions/{}/{}/history", + "rationale": "Definition history and interactive report detail UI is tracked by the reporting surface work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/5" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/reporting/executions/{}", + "rationale": "Execution detail and saved-view management UI is tracked by the reporting surface work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/5" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/reporting/provider-executions/{}", + "rationale": "Stable retained-execution retrieval supports authorized API rehydration; the current WebUI consumes the execution response directly and does not need an extra fetch.", + "repository": "govoplan-reporting" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/reporting/executions/{}/publications", + "rationale": "Execution detail and saved-view management UI is tracked by the reporting surface work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/5" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/reporting/imports/assessments", + "rationale": "Governed source/import assessment UI is tracked by the semantic-model work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/reporting/imports/assessments", + "rationale": "Governed source/import assessment UI is tracked by the semantic-model work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/4" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/reporting/quality-plans/{}/runs", + "rationale": "Quality-plan execution UI is tracked by the interactive reporting work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/5" + }, + { + "category": "missing_ui", + "method": "DELETE", + "path": "/reporting/saved-views/{}", + "rationale": "Saved-view lifecycle UI is tracked by the reporting surface work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/5" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/reporting/schedules", + "rationale": "Schedule and publication administration UI is tracked by the publication-provider work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/6" + }, + { + "category": "worker_internal", + "method": "POST", + "path": "/reporting/schedules/dispatch", + "rationale": "Operational worker, scheduler, reconciliation, or health endpoint; it is not a direct user surface.", + "repository": "govoplan-reporting" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/reporting/sources/dataflow", + "rationale": "Provider-neutral source catalogue UI is tracked by the semantic-model work.", + "repository": "govoplan-reporting", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/4" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/rest/status", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-rest" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/risk-compliance/assurance/edge-history", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-risk-compliance" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/risk-compliance/assurance/node-history", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-risk-compliance" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/risk-compliance/sanctions/rescreening-requirements", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-risk-compliance" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/risk-compliance/sanctions/screenings/{}", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-risk-compliance" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/risk-compliance/sanctions/screenings/{}/freshness", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-risk-compliance" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/scheduling/requests/{}/cancel", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-scheduling" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/search/admin/changes/process", + "rationale": "Search diagnostics, reconciliation, and rebuild operator UI remains in the Search delivery epic.", + "repository": "govoplan-search", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/1" + }, + { + "category": "missing_ui", + "method": "GET", + "path": "/search/admin/diagnostics", + "rationale": "Search diagnostics, reconciliation, and rebuild operator UI remains in the Search delivery epic.", + "repository": "govoplan-search", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/1" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/search/admin/rebuilds/{}/{}/continue", + "rationale": "Search diagnostics, reconciliation, and rebuild operator UI remains in the Search delivery epic.", + "repository": "govoplan-search", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/1" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/search/admin/rebuilds/{}/{}/start", + "rationale": "Search diagnostics, reconciliation, and rebuild operator UI remains in the Search delivery epic.", + "repository": "govoplan-search", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/1" + }, + { + "category": "missing_ui", + "method": "POST", + "path": "/search/admin/reconcile-modules", + "rationale": "Search diagnostics, reconciliation, and rebuild operator UI remains in the Search delivery epic.", + "repository": "govoplan-search", + "tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/1" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/services/availability", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-services" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/services/definitions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-services" + }, + { + "category": "intentionally_headless", + "method": "POST", + "path": "/services/definitions", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-services" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/services/definitions/{}", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-services" + }, + { + "category": "public_integration", + "method": "GET", + "path": "/soap/status", + "rationale": "Published integration, interoperability, public-participant, or health endpoint.", + "repository": "govoplan-soap" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/admin/tenants/{}/deletion-plan", + "rationale": "Tenant deletion preflight is an administrative safety API consumed before a destructive workflow.", + "repository": "govoplan-tenancy" + }, + { + "category": "compatibility", + "method": "POST", + "path": "/tenancy/switch-tenant", + "rationale": "Retained compatibility endpoint; current module surfaces use the replacement contract.", + "repository": "govoplan-tenancy" + }, + { + "category": "ui_reachable", + "method": "GET", + "path": "/views/surfaces", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-views" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/voting/{}/annul", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-voting" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/voting/{}/certify", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-voting" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/voting/{}/challenge", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-voting" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/voting/{}/close", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-voting" + }, + { + "category": "ui_reachable", + "method": "POST", + "path": "/voting/{}/open", + "rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.", + "repository": "govoplan-voting" + }, + { + "category": "intentionally_headless", + "method": "GET", + "path": "/workflow/definitions/{}/triggers", + "rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.", + "repository": "govoplan-workflow-engine" + } + ], + "schema_version": 1 +} diff --git a/tools/inventory/platform-interface-inventory.py b/tools/inventory/platform-interface-inventory.py index 1e607a5..70d07e8 100644 --- a/tools/inventory/platform-interface-inventory.py +++ b/tools/inventory/platform-interface-inventory.py @@ -19,6 +19,18 @@ from typing import Any META_ROOT = Path(__file__).resolve().parents[2] HTTP_METHODS = {"delete", "get", "head", "options", "patch", "post", "put"} PATH_PARAMETER = re.compile(r"\$\{[^{}]*\}|\{[^{}]+\}") +ENDPOINT_SURFACE_CATEGORIES = { + "ui_reachable", + "intentionally_headless", + "public_integration", + "worker_internal", + "compatibility", + "missing_ui", + "removable", +} +DEFAULT_ENDPOINT_DECLARATIONS = ( + META_ROOT / "tools" / "inventory" / "endpoint-surface-declarations.json" +) def main() -> int: @@ -31,21 +43,29 @@ def main() -> int: parser.add_argument( "--strict", action="store_true", - help="Fail when a used translation key is absent from a generated locale catalog.", + help="Fail on missing translations or incomplete endpoint-surface declarations.", + ) + parser.add_argument( + "--endpoint-declarations", + type=Path, + default=DEFAULT_ENDPOINT_DECLARATIONS, + help="Versioned endpoint-surface declaration registry.", ) args = parser.parse_args() - catalog = json.loads( - (META_ROOT / "repositories.json").read_text(encoding="utf-8") - ) + catalog = json.loads((META_ROOT / "repositories.json").read_text(encoding="utf-8")) workspace_root = Path(catalog["default_parent"]).resolve() webui = _extract_webui() backend_endpoints = _extract_backend_endpoints(catalog, workspace_root) manifests = _extract_manifests(catalog, workspace_root) + endpoint_declarations = _load_endpoint_declarations( + args.endpoint_declarations.resolve() + ) inventory = _assemble_inventory( webui=webui, backend_endpoints=backend_endpoints, manifests=manifests, + endpoint_declarations=endpoint_declarations, ) output_dir = args.output_dir.resolve() @@ -60,12 +80,26 @@ def main() -> int: print(f"Platform inventory JSON: {json_path}") print(f"Platform inventory summary: {markdown_path}") - if args.strict and inventory["translation_health"]["missing_catalog_entries"]: - print( - "Used translation keys are missing from generated catalogs.", - file=sys.stderr, - ) - return 1 + 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 failures: + print( + "Strict platform inventory failed: " + "; ".join(failures) + ".", + file=sys.stderr, + ) + return 1 return 0 @@ -167,7 +201,9 @@ def _endpoint_from_decorator( route = _static_string(decorator.args[0]) if route is None: return None - owner = decorator.func.value.id if isinstance(decorator.func.value, ast.Name) else "" + owner = ( + decorator.func.value.id if isinstance(decorator.func.value, ast.Name) else "" + ) prefix = prefixes.get(owner, "") return { "method": method.upper(), @@ -247,6 +283,7 @@ def _assemble_inventory( webui: dict[str, Any], backend_endpoints: list[dict[str, Any]], manifests: list[dict[str, Any]], + endpoint_declarations: dict[tuple[str, str, str], dict[str, Any]], ) -> dict[str, Any]: frontend_refs = webui["frontendApiReferences"] frontend_paths = { @@ -254,25 +291,61 @@ def _assemble_inventory( for reference in frontend_refs if canonical_api_path(reference["path"]) } + endpoint_keys = {endpoint_key(endpoint) for endpoint in backend_endpoints} + classified_endpoints: list[dict[str, Any]] = [] + for endpoint in backend_endpoints: + key = endpoint_key(endpoint) + canonical_path = key[2] + static_webui_reference = canonical_path in frontend_paths + declaration = endpoint_declarations.get(key) + surface = ( + { + "category": "ui_reachable", + "rationale": "A canonical API path reference exists in WebUI source.", + "source": "static_webui_scan", + } + if static_webui_reference + else ( + {**declaration, "source": "declaration"} + if declaration is not None + else None + ) + ) + classified_endpoints.append( + { + **endpoint, + "canonical_path": canonical_path, + "static_webui_reference": static_webui_reference, + "surface": surface, + } + ) unreferenced = [ endpoint - for endpoint in backend_endpoints - if canonical_api_path(endpoint["path"]) not in frontend_paths + for endpoint in classified_endpoints + if not endpoint["static_webui_reference"] ] + unclassified = [ + endpoint for endpoint in unreferenced if endpoint["surface"] is None + ] + stale_declarations = [ + declaration + for key, declaration in endpoint_declarations.items() + if key not in endpoint_keys + ] + classification_counts = Counter( + endpoint["surface"]["category"] + for endpoint in classified_endpoints + if endpoint["surface"] is not None + ) usages = {item["key"] for item in webui["translationUsages"]} catalogs = webui["translationCatalog"] - catalog_keys = { - locale: set(entries) - for locale, entries in catalogs.items() - } + catalog_keys = {locale: set(entries) for locale, entries in catalogs.items()} expected_locales = sorted(catalog_keys) missing_catalog_entries = [ { "key": key, "missing_locales": [ - locale - for locale in expected_locales - if key not in catalog_keys[locale] + locale for locale in expected_locales if key not in catalog_keys[locale] ], } for key in sorted(usages) @@ -311,9 +384,12 @@ def _assemble_inventory( "missing_catalog_entries": missing_catalog_entries, }, "api": { - "backend_endpoints": backend_endpoints, + "backend_endpoints": classified_endpoints, "frontend_references": frontend_refs, "unreferenced_by_static_webui_scan": unreferenced, + "unclassified_endpoints": unclassified, + "stale_endpoint_declarations": stale_declarations, + "classification_counts": dict(sorted(classification_counts.items())), }, "summary": { "modules": len(manifests), @@ -326,6 +402,8 @@ def _assemble_inventory( "backend_endpoints": len(backend_endpoints), "frontend_api_references": len(frontend_refs), "backend_endpoints_without_static_webui_reference": len(unreferenced), + "unclassified_backend_endpoints": len(unclassified), + "stale_endpoint_declarations": len(stale_declarations), }, } @@ -340,6 +418,7 @@ def _render_markdown(inventory: dict[str, Any]) -> str: item["repository"] for item in inventory["api"]["unreferenced_by_static_webui_scan"] ) + classification_counts = inventory["api"]["classification_counts"] lines = [ "# GovOPlaN Platform Interface Inventory", "", @@ -360,6 +439,8 @@ def _render_markdown(inventory: dict[str, Any]) -> str: "- Backend endpoints without a static WebUI reference: " f"{summary['backend_endpoints_without_static_webui_reference']}" ), + f"- Unclassified backend endpoints: {summary['unclassified_backend_endpoints']}", + f"- Stale endpoint declarations: {summary['stale_endpoint_declarations']}", f"- Used translation keys missing from a locale catalog: {len(missing)}", "", "## Help Review Candidates", @@ -388,6 +469,19 @@ def _render_markdown(inventory: dict[str, Any]) -> str: f"| `{repository}` | {count} |" for repository, count in sorted(endpoint_by_repository.items()) ) + lines.extend( + [ + "", + "## Endpoint Surface Classifications", + "", + "| Classification | Endpoints |", + "| --- | ---: |", + ] + ) + lines.extend( + f"| `{category}` | {count} |" + for category, count in sorted(classification_counts.items()) + ) lines.extend( [ "", @@ -408,11 +502,89 @@ def canonical_api_path(value: str) -> str: if "/api/" in path: path = path[path.index("/api/") :] path = re.sub(r"^/api/v\d+", "", path) + path = re.sub(r"(?<=[^/])\$\{[^{}]*\}$", "", path) path = PATH_PARAMETER.sub("{}", path) path = re.sub(r"/+", "/", path) return path.rstrip("/") or "/" +def endpoint_key(endpoint: dict[str, Any]) -> tuple[str, str, str]: + return ( + str(endpoint["repository"]), + str(endpoint["method"]).upper(), + canonical_api_path(str(endpoint["path"])), + ) + + +def _load_endpoint_declarations( + path: Path, +) -> dict[tuple[str, str, str], dict[str, Any]]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as exc: + raise ValueError( + f"Endpoint declaration registry does not exist: {path}" + ) from exc + except json.JSONDecodeError as exc: + raise ValueError( + f"Endpoint declaration registry is invalid JSON: {exc}" + ) from exc + if not isinstance(payload, dict) or payload.get("schema_version") != 1: + raise ValueError("Endpoint declaration registry must use schema_version 1.") + entries = payload.get("endpoints") + if not isinstance(entries, list): + raise ValueError( + "Endpoint declaration registry must contain an endpoints list." + ) + declarations: dict[tuple[str, str, str], dict[str, Any]] = {} + for index, entry in enumerate(entries): + if not isinstance(entry, dict): + raise ValueError(f"Endpoint declaration {index} must be an object.") + repository = entry.get("repository") + method = entry.get("method") + raw_path = entry.get("path") + category = entry.get("category") + rationale = entry.get("rationale") + if not isinstance(repository, str) or not repository.strip(): + raise ValueError(f"Endpoint declaration {index} has no repository.") + if not isinstance(method, str) or method.lower() not in HTTP_METHODS: + raise ValueError(f"Endpoint declaration {index} has an invalid method.") + if not isinstance(raw_path, str) or not raw_path.startswith("/"): + raise ValueError(f"Endpoint declaration {index} has an invalid path.") + canonical_path = canonical_api_path(raw_path) + if raw_path != canonical_path: + raise ValueError( + f"Endpoint declaration {index} path must be canonical: {canonical_path}" + ) + if category not in ENDPOINT_SURFACE_CATEGORIES: + raise ValueError(f"Endpoint declaration {index} has an invalid category.") + if not isinstance(rationale, str) or not rationale.strip(): + raise ValueError(f"Endpoint declaration {index} has no rationale.") + tracking_issue = entry.get("tracking_issue") + if category == "missing_ui" and ( + not isinstance(tracking_issue, str) or not tracking_issue.strip() + ): + raise ValueError( + f"Endpoint declaration {index} requires a tracking_issue for missing_ui." + ) + key = (repository, method.upper(), canonical_path) + if key in declarations: + raise ValueError(f"Duplicate endpoint declaration: {key!r}.") + declarations[key] = { + "repository": repository, + "method": method.upper(), + "path": canonical_path, + "category": category, + "rationale": rationale.strip(), + **( + {"tracking_issue": tracking_issue.strip()} + if isinstance(tracking_issue, str) and tracking_issue.strip() + else {} + ), + } + return declarations + + def _join_route(prefix: str, route: str) -> str: return f"/{prefix.strip('/')}/{route.strip('/')}".replace("//", "/") @@ -444,17 +616,11 @@ def _call_name(node: ast.AST) -> str: def _plain_value(value: Any) -> Any: if is_dataclass(value): - return { - key: _plain_value(item) - for key, item in asdict(value).items() - } + return {key: _plain_value(item) for key, item in asdict(value).items()} if isinstance(value, tuple): return [_plain_value(item) for item in value] if isinstance(value, dict): - return { - str(key): _plain_value(item) - for key, item in value.items() - } + return {str(key): _plain_value(item) for key, item in value.items()} return value