10 Commits
Author SHA1 Message Date
zemion 6c2b36af0f feat(inventory): enforce high-risk contextual help
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 12m18s
Developer Meta-package Release / publish-package (push) Successful in 10s
Dependency Audit / dependency-audit (push) Successful in 1m40s
2026-08-24 11:40:21 +02:00
zemion 3f75ca8e48 chore: compose German documentation releases
Dependency Audit / dependency-audit (push) Successful in 1m37s
Deployment Installer / deployment-installer (push) Successful in 5s
Security Audit / security-audit (push) Failing after 11m47s
2026-08-24 01:47:06 +02:00
zemion a886a9b3de chore(release): compose complete German coverage
Deployment Installer / deployment-installer (push) Successful in 6s
Dependency Audit / dependency-audit (push) Successful in 1m48s
Security Audit / security-audit (push) Failing after 12m0s
2026-08-23 21:31:07 +02:00
zemion fe83290d56 chore(release): compose expanded German coverage
Dependency Audit / dependency-audit (push) Successful in 1m48s
Deployment Installer / deployment-installer (push) Successful in 7s
Security Audit / security-audit (push) Failing after 12m5s
2026-08-23 20:51:28 +02:00
zemion c50f699399 chore(release): compose German reference coverage
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m55s
2026-08-23 19:57:05 +02:00
zemion 59b45a0829 chore(release): compose autonomous integration contracts
Dependency Audit / dependency-audit (push) Successful in 1m48s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m57s
2026-08-23 18:14:11 +02:00
zemion 861abcc573 chore(release): compose integration foundations
Dependency Audit / dependency-audit (push) Successful in 1m46s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m53s
2026-08-23 11:21:04 +02:00
zemion 5e995fed88 chore(release): compose German documentation batch
Dependency Audit / dependency-audit (push) Successful in 1m37s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m4s
2026-08-23 02:14:42 +02:00
zemion 41ca242004 chore(release): compose Reporting and Search 0.1.19
Dependency Audit / dependency-audit (push) Successful in 1m41s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m51s
2026-08-22 21:14:24 +02:00
zemion 85caa8d337 chore(release): integrate structured documentation localization
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 12m2s
Developer Meta-package Release / publish-package (push) Successful in 10s
2026-08-22 20:45:41 +02:00
10 changed files with 487 additions and 97 deletions
@@ -5,9 +5,9 @@ A migration-owning module must register and document its canonical DSAR provider
Every other active module requires a reviewed explanation of why it owns no
persistent subject-data store. Adding a migration invalidates that explanation.
- Active modules: 68
- Active modules: 72
- Registered and documented DSAR providers: 48
- Reviewed no-store rationales: 20
- Reviewed no-store rationales: 24
- Unexplained coverage gaps: 0
| Module | Repository | Persistence | Coverage | Rationale |
@@ -32,11 +32,14 @@ persistent subject-data store. Adding a migration invalidates that explanation.
| `datasources` | `govoplan-datasources` | Migration-owned | Provider | Provider `privacy.dsar.datasources` is registered and documented. |
| `decisions` | `govoplan-decisions` | Migration-owned | Provider | Provider `privacy.dsar.decisions` is registered and documented. |
| `dist_lists` | `govoplan-dist-lists` | Migration-owned | Provider | Provider `privacy.dsar.dist_lists` is registered and documented. |
| `dms` | `govoplan-dms` | No module migration | Reviewed no-store rationale | Stateless integration-preview module: DMS retains no document, person, credential, or provider-response store; Files and Records remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, or diagnostic. |
| `docs` | `govoplan-docs` | Migration-owned | Provider | Provider `privacy.dsar.docs` is registered and documented. |
| `encryption` | `govoplan-encryption` | Migration-owned | Provider | Provider `privacy.dsar.encryption` is registered and documented. |
| `erp` | `govoplan-erp` | No module migration | Reviewed no-store rationale | Stateless integration-contract module: ERP retains no invoice, payable, plan, booking observation, provider response, or credential store; Procurement, Payments, Ledger, Files, and Audit remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, reconciliation decision, or diagnostic. |
| `evaluation` | `govoplan-evaluation` | No module migration | Reviewed no-store rationale | Contract-only module: evaluation runs, responses, and scores are not persisted; reassess before adding a migration-owned store. |
| `facilities` | `govoplan-facilities` | No module migration | Reviewed no-store rationale | Contract-only module: facility and maintenance persistence are not implemented; reassess before adding a migration-owned store. |
| `files` | `govoplan-files` | Migration-owned | Provider | Provider `privacy.dsar.files` is registered and documented. |
| `fit_connect` | `govoplan-fit-connect` | No module migration | Reviewed no-store rationale | Stateless transport-contract module: FIT-Connect retains no submission, attachment, receipt, acknowledgement plan, key, provider response, or diagnostic store; the owning Service, Forms, Cases, Files, and Audit workflows remain responsible for subject data. Reassess before persisting any ingress or event-log evidence. |
| `forms` | `govoplan-forms` | Migration-owned | Provider | Provider `privacy.dsar.forms` is registered and documented. |
| `forms_runtime` | `govoplan-forms-runtime` | Migration-owned | Provider | Provider `privacy.dsar.forms_runtime` is registered and documented. |
| `grants` | `govoplan-grants` | No module migration | Reviewed no-store rationale | Contract-only module: grant applications, awards, and monitoring are not persisted; reassess before adding a migration-owned store. |
@@ -80,6 +83,7 @@ persistent subject-data store. Adding a migration invalidates that explanation.
| `wiki` | `govoplan-wiki` | Migration-owned | Provider | Provider `privacy.dsar.wiki` is registered and documented. |
| `workflow` | `govoplan-workflow` | No module migration | Reviewed no-store rationale | Presentation-only module: Workflow edits and projects Workflow Engine state; Workflow Engine owns persistence and DSAR coverage. |
| `workflow_engine` | `govoplan-workflow-engine` | Migration-owned | Provider | Provider `privacy.dsar.workflow_engine` is registered and documented. |
| `xrechnung` | `govoplan-xrechnung` | No module migration | Reviewed no-store rationale | Stateless validation-contract module: XRechnung persists no invoice, report, diagnostic, or handoff; the invoking Files, Procurement, or Payments workflow remains the subject-data owner. Reassess before adding a validation store. |
Provider search, export minimization, retention, and erasure behavior remains
documented and tested by each owning module. This matrix verifies adoption and
+72 -68
View File
@@ -4,85 +4,89 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan"
version = "0.1.36"
version = "0.1.39"
description = "Developer convenience package for a versioned GovOPlaN composition"
readme = "README.md"
requires-python = ">=3.12"
license = { text = "AGPL-3.0-or-later" }
dependencies = [
"govoplan-core[server]==0.1.36",
"govoplan-tenancy==0.1.18",
"govoplan-organizations==0.1.18",
"govoplan-identity==0.1.18",
"govoplan-idm==0.1.20",
"govoplan-access==0.1.20",
"govoplan-admin==0.1.19",
"govoplan-policy==0.1.20",
"govoplan-audit==0.1.19",
"govoplan-dashboard==0.1.18",
"govoplan-files==0.1.20",
"govoplan-mail==0.1.22",
"govoplan-campaign==0.1.24",
"govoplan-calendar==0.1.18",
"govoplan-docs==0.1.20",
"govoplan-ops==0.1.19",
"govoplan-core[server]==0.1.39",
"govoplan-tenancy==0.1.20",
"govoplan-organizations==0.1.20",
"govoplan-identity==0.1.20",
"govoplan-idm==0.1.24",
"govoplan-access==0.1.23",
"govoplan-admin==0.1.22",
"govoplan-policy==0.1.22",
"govoplan-audit==0.1.20",
"govoplan-dashboard==0.1.20",
"govoplan-files==0.1.23",
"govoplan-mail==0.1.24",
"govoplan-campaign==0.1.27",
"govoplan-calendar==0.1.22",
"govoplan-docs==0.1.22",
"govoplan-ops==0.1.20",
]
[project.optional-dependencies]
full = [
"govoplan-addresses==0.1.18",
"govoplan-approvals==0.1.18",
"govoplan-assets==0.1.19",
"govoplan-booking==0.1.19",
"govoplan-cases==0.1.20",
"govoplan-certificates==0.1.19",
"govoplan-committee==0.1.18",
"govoplan-connectors==0.1.22",
"govoplan-consultation==0.1.19",
"govoplan-contracts==0.1.19",
"govoplan-dataflow==0.1.20",
"govoplan-datasources==0.1.21",
"govoplan-decisions==0.1.18",
"govoplan-dist-lists==0.1.18",
"govoplan-encryption==0.1.18",
"govoplan-evaluation==0.1.19",
"govoplan-facilities==0.1.19",
"govoplan-forms==0.1.20",
"govoplan-forms-runtime==0.1.18",
"govoplan-grants==0.1.19",
"govoplan-helpdesk==0.1.20",
"govoplan-identity-trust==0.1.18",
"govoplan-inspections==0.1.19",
"govoplan-learning==0.1.19",
"govoplan-mandates==0.1.18",
"govoplan-notifications==0.1.18",
"govoplan-parties==0.1.18",
"govoplan-payments==0.1.20",
"govoplan-permits==0.1.19",
"govoplan-poll==0.1.19",
"govoplan-portal==0.1.19",
"govoplan-postbox==0.1.19",
"govoplan-procurement==0.1.19",
"govoplan-projects==0.1.18",
"govoplan-quick-access==0.1.19",
"govoplan-records==0.1.20",
"govoplan-reporting==0.1.18",
"govoplan-resources==0.1.19",
"govoplan-addresses==0.1.21",
"govoplan-approvals==0.1.20",
"govoplan-assets==0.1.20",
"govoplan-booking==0.1.20",
"govoplan-cases==0.1.22",
"govoplan-certificates==0.1.20",
"govoplan-committee==0.1.20",
"govoplan-connectors==0.1.25",
"govoplan-consultation==0.1.20",
"govoplan-contracts==0.1.20",
"govoplan-dataflow==0.1.23",
"govoplan-datasources==0.1.24",
"govoplan-decisions==0.1.19",
"govoplan-dist-lists==0.1.20",
"govoplan-dms==0.1.20",
"govoplan-encryption==0.1.19",
"govoplan-erp==0.1.20",
"govoplan-evaluation==0.1.20",
"govoplan-facilities==0.1.20",
"govoplan-fit-connect==0.1.20",
"govoplan-forms==0.1.22",
"govoplan-forms-runtime==0.1.19",
"govoplan-grants==0.1.20",
"govoplan-helpdesk==0.1.21",
"govoplan-identity-trust==0.1.20",
"govoplan-inspections==0.1.20",
"govoplan-learning==0.1.20",
"govoplan-mandates==0.1.19",
"govoplan-notifications==0.1.19",
"govoplan-parties==0.1.19",
"govoplan-payments==0.1.21",
"govoplan-permits==0.1.20",
"govoplan-poll==0.1.20",
"govoplan-portal==0.1.21",
"govoplan-postbox==0.1.21",
"govoplan-procurement==0.1.20",
"govoplan-projects==0.1.19",
"govoplan-quick-access==0.1.20",
"govoplan-records==0.1.22",
"govoplan-reporting==0.1.20",
"govoplan-resources==0.1.20",
"govoplan-rest==0.1.19",
"govoplan-risk-compliance==0.1.18",
"govoplan-scheduling==0.1.18",
"govoplan-search==0.1.18",
"govoplan-services==0.1.18",
"govoplan-risk-compliance==0.1.20",
"govoplan-scheduling==0.1.21",
"govoplan-search==0.1.19",
"govoplan-services==0.1.19",
"govoplan-soap==0.1.19",
"govoplan-tasks==0.1.20",
"govoplan-templates==0.1.18",
"govoplan-tickets==0.1.20",
"govoplan-transparency==0.1.19",
"govoplan-views==0.1.19",
"govoplan-voting==0.1.18",
"govoplan-wiki==0.1.20",
"govoplan-workflow==0.1.21",
"govoplan-workflow-engine==0.1.19",
"govoplan-tasks==0.1.21",
"govoplan-templates==0.1.21",
"govoplan-tickets==0.1.22",
"govoplan-transparency==0.1.20",
"govoplan-views==0.1.21",
"govoplan-voting==0.1.20",
"govoplan-wiki==0.1.22",
"govoplan-workflow==0.1.22",
"govoplan-workflow-engine==0.1.21",
"govoplan-xrechnung==0.1.21",
]
[project.urls]
+15 -15
View File
@@ -1,18 +1,18 @@
# Whole-product release install from immutable, independently versioned module tags.
# Only add a module after its referenced tag has been published.
../govoplan-core[server]
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.18
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.18
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.18
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.20
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.20
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.19
govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.20
govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.19
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.18
govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.20
govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.22
govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.24
govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.18
govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.20
govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.19
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.20
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.20
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.20
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.24
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.23
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.22
govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.22
govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.20
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.20
govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.23
govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.24
govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.27
govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.22
govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.22
govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.20
+1 -1
View File
@@ -70,7 +70,7 @@ class PackageRegistryReleaseTests(unittest.TestCase):
by_name = {item["name"]: item for item in selected}
self.assertIn("govoplan-core", by_name)
self.assertIn("govoplan-records", by_name)
self.assertEqual("0.1.20", by_name["govoplan-tasks"]["version"])
self.assertEqual("0.1.21", by_name["govoplan-tasks"]["version"])
payload = PACKAGE_SET.generate_package_set(
core_version=core_version,
+9 -4
View File
@@ -22,11 +22,16 @@ class PackageSetDispatchTests(unittest.TestCase):
def test_meta_package_resolves_to_exact_tagged_repository_targets(self) -> None:
targets = MODULE.package_targets()
self.assertEqual(66, len(targets))
self.assertEqual(66, len({target.distribution for target in targets}))
self.assertEqual(73, len(targets))
self.assertEqual(73, len({target.distribution for target in targets}))
by_name = {target.distribution: target for target in targets}
self.assertEqual("v0.1.14", by_name["govoplan-core"].tag)
self.assertEqual("v0.1.8", by_name["govoplan-access"].tag)
self.assertEqual("v0.1.38", by_name["govoplan-core"].tag)
self.assertEqual("v0.1.22", by_name["govoplan-access"].tag)
self.assertEqual("v0.1.20", by_name["govoplan-dms"].tag)
self.assertEqual("v0.1.20", by_name["govoplan-erp"].tag)
self.assertEqual("v0.1.20", by_name["govoplan-fit-connect"].tag)
self.assertEqual("v0.1.23", by_name["govoplan-idm"].tag)
self.assertEqual("v0.1.21", by_name["govoplan-xrechnung"].tag)
self.assertTrue(by_name["govoplan-core"].tag_exists)
self.assertTrue(by_name["govoplan-access"].has_webui)
self.assertEqual(
@@ -169,6 +169,69 @@ class PlatformInterfaceInventoryTests(unittest.TestCase):
),
)
def test_high_risk_help_baseline_is_validated(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "help-baseline.json"
path.write_text(
json.dumps(
{
"schema_version": 1,
"maximum_missing_exact_help": 3,
}
),
encoding="utf-8",
)
self.assertEqual(
3,
inventory._load_high_risk_help_baseline(path)[
"maximum_missing_exact_help"
],
)
path.write_text(
json.dumps(
{
"schema_version": 1,
"maximum_missing_exact_help": -1,
}
),
encoding="utf-8",
)
with self.assertRaisesRegex(ValueError, "non-negative integer"):
inventory._load_high_risk_help_baseline(path)
def test_declaration_strict_mode_rejects_high_risk_help_regression(
self,
) -> None:
result = {
"translation_health": {"missing_catalog_entries": []},
"api": {
"unclassified_endpoints": [],
"stale_endpoint_declarations": [],
},
"declaration_health": {},
"help_health": {
"invalid_risk_annotations": [],
"unresolved_exact_high_risk_help": [],
"high_risk_help_without_german": [],
"missing_exact_high_risk_help": [{"id": "example.delete"}],
"baseline_maximum_missing": 0,
"baseline_regression": True,
},
}
self.assertEqual(
[
"1 high-risk controls lack exact F1 help; baseline permits at most 0"
],
inventory._strict_failures(
result,
check_translations=False,
check_endpoints=False,
check_declarations=True,
),
)
def test_fastapi_route_scanner_includes_router_prefix(self) -> None:
tree = ast.parse(
"""
+5 -1
View File
@@ -4,8 +4,11 @@
"certificates": "Contract-only module: certificate issuance and revocation persistence are not implemented; reassess before adding a migration-owned store.",
"consultation": "Contract-only module: consultation submissions and evaluation persistence are not implemented; reassess before adding a migration-owned store.",
"contracts": "Contract-only module: contract, amendment, and obligation persistence are not implemented; reassess before adding a migration-owned store.",
"dms": "Stateless integration-preview module: DMS retains no document, person, credential, or provider-response store; Files and Records remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, or diagnostic.",
"erp": "Stateless integration-contract module: ERP retains no invoice, payable, plan, booking observation, provider response, or credential store; Procurement, Payments, Ledger, Files, and Audit remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, reconciliation decision, or diagnostic.",
"evaluation": "Contract-only module: evaluation runs, responses, and scores are not persisted; reassess before adding a migration-owned store.",
"facilities": "Contract-only module: facility and maintenance persistence are not implemented; reassess before adding a migration-owned store.",
"fit_connect": "Stateless transport-contract module: FIT-Connect retains no submission, attachment, receipt, acknowledgement plan, key, provider response, or diagnostic store; the owning Service, Forms, Cases, Files, and Audit workflows remain responsible for subject data. Reassess before persisting any ingress or event-log evidence.",
"grants": "Contract-only module: grant applications, awards, and monitoring are not persisted; reassess before adding a migration-owned store.",
"inspections": "Contract-only module: inspections, findings, and measures are not persisted; reassess before adding a migration-owned store.",
"learning": "Contract-only module: learning offers, enrollment, and completion are not persisted; reassess before adding a migration-owned store.",
@@ -18,5 +21,6 @@
"soap": "Transport-only module: SOAP binds explicitly published operations and owns no domain or subject-data store.",
"tenancy": "Orchestration module: tenant lifecycle and settings use Core-owned storage; Access covers account and membership subject data.",
"transparency": "Contract-only module: requests, disclosure reviews, and publications are not persisted; reassess before adding a migration-owned store.",
"workflow": "Presentation-only module: Workflow edits and projects Workflow Engine state; Workflow Engine owns persistence and DSAR coverage."
"workflow": "Presentation-only module: Workflow edits and projects Workflow Engine state; Workflow Engine owns persistence and DSAR coverage.",
"xrechnung": "Stateless validation-contract module: XRechnung persists no invoice, report, diagnostic, or handoff; the invoking Files, Procurement, or Payments workflow remains the subject-data owner. Reassess before adding a validation store."
}
+125 -4
View File
@@ -63,6 +63,28 @@ const helpAttributes = new Set([
"helperText",
"helpText"
]);
const exactHelpAttributes = new Set([
"data-help-context-id",
"helpContextId"
]);
const helpRiskAttributes = new Set([
"data-help-risk",
"helpRisk"
]);
const reviewedHelpRiskAttributes = new Set([
"data-help-risk-reviewed",
"helpRiskReviewed"
]);
const supportedHelpRisks = new Set([
"authority",
"credential",
"disclosure",
"encryption",
"external-effect",
"irreversible",
"policy",
"retention"
]);
const actionComponentPattern = /(?:Action|Button|Link)$/;
const contributionTypes = new Map([
["AdminSectionsUiCapability", "admin_section"],
@@ -200,20 +222,43 @@ function inspectSource(repository, sourceRoot, sourcePath) {
const parentAttributes = parentFormField
? jsxAttributes(parentFormField)
: new Map();
const scopedAncestorAttributes = nearestScopedHelpAttributes(node);
const label =
attributes.get("label") ??
attributes.get("aria-label") ??
parentAttributes.get("label") ??
null;
const help = firstAttribute(attributes, helpAttributes) ??
firstAttribute(parentAttributes, helpAttributes);
firstAttribute(parentAttributes, helpAttributes) ??
firstAttribute(scopedAncestorAttributes, helpAttributes);
const hasHelp = hasAnyAttribute(attributes, helpAttributes) ||
hasAnyAttribute(parentAttributes, helpAttributes);
hasAnyAttribute(parentAttributes, helpAttributes) ||
hasAnyAttribute(scopedAncestorAttributes, helpAttributes);
const hasExactHelp = hasAnyAttribute(attributes, exactHelpAttributes) ||
hasAnyAttribute(parentAttributes, exactHelpAttributes) ||
hasAnyAttribute(scopedAncestorAttributes, exactHelpAttributes);
const helpContextId = firstAttribute(attributes, exactHelpAttributes) ??
firstAttribute(parentAttributes, exactHelpAttributes) ??
firstAttribute(scopedAncestorAttributes, exactHelpAttributes);
const explicitId = firstAttribute(
attributes,
new Set(["interfaceId", "data-interface-id", "id", "name", "field"])
);
const context = nearestNamedContext(node);
const risk = helpRiskFor({
component,
context,
file: relativeFile,
label,
explicitId,
name: attributes.get("name") ?? attributes.get("id") ?? attributes.get("field") ?? null,
explicitRisk: firstAttribute(attributes, helpRiskAttributes) ??
firstAttribute(parentAttributes, helpRiskAttributes) ??
firstAttribute(scopedAncestorAttributes, helpRiskAttributes)
});
const riskReviewed = firstAttribute(attributes, reviewedHelpRiskAttributes) ??
firstAttribute(parentAttributes, reviewedHelpRiskAttributes) ??
firstAttribute(scopedAncestorAttributes, reviewedHelpRiskAttributes);
const stableId = sourceIdentity(
"field",
node,
@@ -237,7 +282,14 @@ function inspectSource(repository, sourceRoot, sourcePath) {
help: help ?? null,
helpId: hasHelp ? `${stableId}.help` : null,
helpDynamic: hasHelp && help === null,
helpCandidate: !hasHelp
helpCandidate: !hasHelp,
helpExact: hasExactHelp,
helpContextId,
helpContextDynamic: hasExactHelp && helpContextId === null,
helpRisk: risk.value,
helpRiskSource: risk.source,
helpRiskReviewed: riskReviewed,
highRiskHelpMissing: risk.value !== null && !hasExactHelp && riskReviewed !== "standard"
});
}
@@ -261,6 +313,19 @@ function inspectSource(repository, sourceRoot, sourcePath) {
new Set(["interfaceId", "data-interface-id", "id", "name"])
);
const context = nearestNamedContext(node);
const hasHelp = hasAnyAttribute(attributes, helpAttributes);
const hasExactHelp = hasAnyAttribute(attributes, exactHelpAttributes);
const helpContextId = firstAttribute(attributes, exactHelpAttributes);
const risk = helpRiskFor({
component,
context,
file: relativeFile,
label,
explicitId,
name: attributes.get("name") ?? attributes.get("id") ?? null,
explicitRisk: firstAttribute(attributes, helpRiskAttributes)
});
const riskReviewed = firstAttribute(attributes, reviewedHelpRiskAttributes);
result.actions.push({
...locate(node),
id: sourceIdentity(
@@ -273,7 +338,15 @@ function inspectSource(repository, sourceRoot, sourcePath) {
idSource: explicitId === null ? "source_anchor" : "explicit",
context,
component,
label
label,
helpExact: hasExactHelp,
helpContextId,
helpContextDynamic: hasExactHelp && helpContextId === null,
helpDynamic: hasHelp && firstAttribute(attributes, helpAttributes) === null,
helpRisk: risk.value,
helpRiskSource: risk.source,
helpRiskReviewed: riskReviewed,
highRiskHelpMissing: risk.value !== null && !hasExactHelp && riskReviewed !== "standard"
});
}
@@ -354,6 +427,26 @@ function inspectSource(repository, sourceRoot, sourcePath) {
return null;
}
function nearestScopedHelpAttributes(node) {
let current = node.parent;
while (current) {
if (ts.isJsxElement(current)) {
const attributes = jsxAttributes(current.openingElement);
if (attributes.get("data-help-scope") === "field") return attributes;
}
if (
ts.isFunctionDeclaration(current) ||
ts.isMethodDeclaration(current) ||
ts.isArrowFunction(current) ||
ts.isFunctionExpression(current)
) {
return new Map();
}
current = current.parent;
}
return new Map();
}
function jsxAttributes(node) {
const mapped = new Map();
for (const attribute of node.attributes.properties) {
@@ -579,6 +672,34 @@ function hasAnyAttribute(attributes, names) {
return false;
}
function helpRiskFor({ component, context, file, label, explicitId, name, explicitRisk }) {
if (typeof explicitRisk === "string") {
return supportedHelpRisks.has(explicitRisk)
? { value: explicitRisk, source: "explicit" }
: { value: null, source: "invalid_explicit" };
}
const value = [component, context, file, label, explicitId, name]
.filter((item) => typeof item === "string")
.join(" ")
.toLowerCase()
.replace(/^i18n:/g, "")
.replace(/[._-]+/g, " ");
const patterns = [
["irreversible", /\b(delete|destroy|erase|purge|dispose|disposition|revoke|withdraw|shred)\b/],
["credential", /\b(credential|password|secret|token|api key|private key)\b/],
["retention", /\b(retention|legal hold|archive lifecycle)\b/],
["encryption", /\b(encrypt|encryption|decrypt|decryption|signing key|signature key)\b/],
["disclosure", /\b(disclose|disclosure|publish|share externally|public export)\b/],
["external-effect", /\b(send|deliver|transfer|refund|payment execution|webhook execution)\b/],
["authority", /\b(grant permission|role assignment|approve|reject|formal decision|mandate)\b/],
["policy", /\b(policy apply|policy override|enforcement mode)\b/]
];
for (const [risk, pattern] of patterns) {
if (pattern.test(value)) return { value: risk, source: "inferred" };
}
return { value: null, source: null };
}
function slug(value) {
const normalized = value
.toLowerCase()
@@ -0,0 +1,5 @@
{
"schema_version": 1,
"maximum_missing_exact_help": 0,
"rationale": "The source-derived high-risk queue for Core issue #284 is fully resolved. Strict declarations reject any new high-risk control without an exact, manifest-declared, German-complete F1 context."
}
+186 -2
View File
@@ -31,6 +31,9 @@ ENDPOINT_SURFACE_CATEGORIES = {
DEFAULT_ENDPOINT_DECLARATIONS = (
META_ROOT / "tools" / "inventory" / "endpoint-surface-declarations.json"
)
DEFAULT_HIGH_RISK_HELP_BASELINE = (
META_ROOT / "tools" / "inventory" / "high-risk-help-baseline.json"
)
REQUIRED_LOCALES = ("de", "en")
REFERENCE_LOCALE = "de"
@@ -75,6 +78,12 @@ def main() -> int:
default=DEFAULT_ENDPOINT_DECLARATIONS,
help="Versioned endpoint-surface declaration registry.",
)
parser.add_argument(
"--high-risk-help-baseline",
type=Path,
default=DEFAULT_HIGH_RISK_HELP_BASELINE,
help="Versioned upper bound for high-risk controls without exact F1 help.",
)
args = parser.parse_args()
catalog = json.loads((META_ROOT / "repositories.json").read_text(encoding="utf-8"))
@@ -85,11 +94,15 @@ def main() -> int:
endpoint_declarations = _load_endpoint_declarations(
args.endpoint_declarations.resolve()
)
high_risk_help_baseline = _load_high_risk_help_baseline(
args.high_risk_help_baseline.resolve()
)
inventory = _assemble_inventory(
webui=webui,
backend_endpoints=backend_endpoints,
manifests=manifests,
endpoint_declarations=endpoint_declarations,
high_risk_help_baseline=high_risk_help_baseline,
runtime_snapshot=(
_load_runtime_snapshot(args.runtime_snapshot.resolve())
if args.runtime_snapshot is not None
@@ -164,6 +177,28 @@ def _strict_failures(
f"{len(declaration_health['stale_runtime_routes'])} runtime route "
"declarations have no WebUI implementation"
)
help_health = inventory.get("help_health", {})
if check_declarations and help_health.get("invalid_risk_annotations"):
failures.append(
f"{len(help_health['invalid_risk_annotations'])} controls use an "
"unsupported contextual-help risk class"
)
if check_declarations and help_health.get("baseline_regression"):
failures.append(
f"{len(help_health['missing_exact_high_risk_help'])} high-risk "
"controls lack exact F1 help; baseline permits at most "
f"{help_health['baseline_maximum_missing']}"
)
if check_declarations and help_health.get("unresolved_exact_high_risk_help"):
failures.append(
f"{len(help_health['unresolved_exact_high_risk_help'])} high-risk "
"controls reference no manifest DocumentationTopic help context"
)
if check_declarations and help_health.get("high_risk_help_without_german"):
failures.append(
f"{len(help_health['high_risk_help_without_german'])} high-risk "
"controls resolve to documentation without complete German content"
)
runtime_comparison = inventory.get("runtime_comparison")
if (
check_declarations
@@ -355,6 +390,29 @@ def _extract_manifests(
}
for permission in manifest.permissions
],
"documentation": [
{
"id": topic.id,
"help_contexts": sorted(
{
str(context)
for context in topic.metadata.get(
"help_contexts", ()
)
if isinstance(context, str) and context.strip()
}
),
"german_complete": (
isinstance(topic.translations.get("de"), dict)
and all(
isinstance(topic.translations["de"].get(field), str)
and topic.translations["de"][field].strip()
for field in ("title", "summary", "body")
)
),
}
for topic in manifest.documentation
],
"architecture": (
manifest.architecture.to_dict()
if manifest.architecture is not None
@@ -400,6 +458,7 @@ def _assemble_inventory(
backend_endpoints: list[dict[str, Any]],
manifests: list[dict[str, Any]],
endpoint_declarations: dict[tuple[str, str, str], dict[str, Any]],
high_risk_help_baseline: dict[str, Any] | None = None,
runtime_snapshot: dict[str, Any] | None = None,
) -> dict[str, Any]:
frontend_refs = webui["frontendApiReferences"]
@@ -471,8 +530,47 @@ def _assemble_inventory(
if any(key not in catalog_keys.get(locale, set()) for locale in expected_locales)
]
fields = webui["fields"]
actions = webui.get("actions", [])
help_candidates = [field for field in fields if field["helpCandidate"]]
dynamic_help = [field for field in fields if field.get("helpDynamic")]
controls = [*fields, *actions]
high_risk_controls = [item for item in controls if item.get("helpRisk")]
missing_exact_high_risk_help = [
item for item in controls if item.get("highRiskHelpMissing")
]
invalid_risk_annotations = [
item
for item in controls
if item.get("helpRiskSource") == "invalid_explicit"
]
documentation_contexts = {
context: {
"module_id": manifest["id"],
"topic_id": topic["id"],
"german_complete": topic["german_complete"],
}
for manifest in manifests
for topic in manifest.get("documentation", [])
for context in topic.get("help_contexts", [])
}
unresolved_exact_high_risk_help = [
item
for item in high_risk_controls
if item.get("helpExact")
and not item.get("helpContextDynamic")
and item.get("helpContextId") not in documentation_contexts
]
high_risk_help_without_german = [
item
for item in high_risk_controls
if item.get("helpContextId") in documentation_contexts
and not documentation_contexts[item["helpContextId"]]["german_complete"]
]
baseline_maximum_missing = (
high_risk_help_baseline["maximum_missing_exact_help"]
if high_risk_help_baseline is not None
else None
)
governance_adoption = Counter(
dimension["adoption"]
for manifest in manifests
@@ -499,10 +597,34 @@ def _assemble_inventory(
"modules": manifests,
"interface_declarations": source_declarations,
"declaration_health": declaration_health,
"help_health": {
"supported_risk_classes": sorted(
{
str(item["helpRisk"])
for item in high_risk_controls
if item.get("helpRisk")
}
),
"high_risk_controls": high_risk_controls,
"missing_exact_high_risk_help": missing_exact_high_risk_help,
"invalid_risk_annotations": invalid_risk_annotations,
"unresolved_exact_high_risk_help": unresolved_exact_high_risk_help,
"high_risk_help_without_german": high_risk_help_without_german,
"dynamic_owner_context_controls": [
item
for item in high_risk_controls
if item.get("helpContextDynamic")
],
"baseline_maximum_missing": baseline_maximum_missing,
"baseline_regression": (
baseline_maximum_missing is not None
and len(missing_exact_high_risk_help) > baseline_maximum_missing
),
},
"runtime_comparison": runtime_comparison,
"ui": {
"fields": fields,
"actions": webui.get("actions", []),
"actions": actions,
"labels": webui["labels"],
"visible_text": webui["visibleText"],
"routes": webui["routes"],
@@ -554,7 +676,19 @@ def _assemble_inventory(
"ui_fields_with_resolvable_f1_context": len(fields),
"help_review_candidates": len(help_candidates),
"dynamic_help_references": len(dynamic_help),
"ui_actions": len(webui.get("actions", [])),
"ui_actions": len(actions),
"high_risk_controls": len(high_risk_controls),
"high_risk_controls_with_exact_help": (
len(high_risk_controls) - len(missing_exact_high_risk_help)
),
"high_risk_controls_missing_exact_help": len(
missing_exact_high_risk_help
),
"invalid_help_risk_annotations": len(invalid_risk_annotations),
"unresolved_exact_high_risk_help": len(
unresolved_exact_high_risk_help
),
"high_risk_help_without_german": len(high_risk_help_without_german),
"interface_declarations": len(source_declarations),
"duplicate_interface_ids": len(declaration_health["duplicate_ids"]),
"undeclared_source_surfaces": len(
@@ -885,6 +1019,10 @@ def _render_markdown(inventory: dict[str, Any]) -> str:
for item in inventory["api"]["unreferenced_by_static_webui_scan"]
)
classification_counts = inventory["api"]["classification_counts"]
high_risk_by_repository = Counter(
item["repository"]
for item in inventory["help_health"]["missing_exact_high_risk_help"]
)
lines = [
"# GovOPlaN Platform Interface Inventory",
"",
@@ -900,6 +1038,12 @@ def _render_markdown(inventory: dict[str, Any]) -> str:
f"- Fields with a resolvable F1 context: {summary['ui_fields_with_resolvable_f1_context']}",
f"- Fields with dynamic help references: {summary['dynamic_help_references']}",
f"- Help review candidates: {summary['help_review_candidates']}",
f"- High-risk controls: {summary['high_risk_controls']}",
f"- High-risk controls with exact F1 help: {summary['high_risk_controls_with_exact_help']}",
f"- High-risk controls missing exact F1 help: {summary['high_risk_controls_missing_exact_help']}",
f"- Invalid help-risk annotations: {summary['invalid_help_risk_annotations']}",
f"- High-risk exact contexts missing a manifest topic: {summary['unresolved_exact_high_risk_help']}",
f"- High-risk contexts without complete German topic content: {summary['high_risk_help_without_german']}",
f"- Stable interface declarations: {summary['interface_declarations']}",
f"- Duplicate interface IDs: {summary['duplicate_interface_ids']}",
f"- WebUI surfaces missing runtime declarations: {summary['undeclared_source_surfaces']}",
@@ -930,6 +1074,23 @@ def _render_markdown(inventory: dict[str, Any]) -> str:
f"| `{repository}` | {count} |"
for repository, count in sorted(help_by_repository.items())
)
lines.extend(
[
"",
"## High-risk Contextual-help Debt",
"",
"Inferred or explicitly classified high-risk controls require an exact",
"F1 context. `data-help-risk-reviewed=\"standard\"` records a reviewed",
"false positive. The versioned baseline makes this queue non-regressing.",
"",
"| Repository | Missing exact contexts |",
"| --- | ---: |",
]
)
lines.extend(
f"| `{repository}` | {count} |"
for repository, count in sorted(high_risk_by_repository.items())
)
lines.extend(
[
"",
@@ -1005,6 +1166,29 @@ def endpoint_key(endpoint: dict[str, Any]) -> tuple[str, str, str]:
)
def _load_high_risk_help_baseline(path: Path) -> dict[str, Any]:
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError as exc:
raise ValueError(
f"High-risk contextual-help baseline does not exist: {path}"
) from exc
except json.JSONDecodeError as exc:
raise ValueError(
f"High-risk contextual-help baseline is invalid JSON: {exc}"
) from exc
if not isinstance(payload, dict) or payload.get("schema_version") != 1:
raise ValueError(
"High-risk contextual-help baseline must use schema_version 1."
)
maximum = payload.get("maximum_missing_exact_help")
if not isinstance(maximum, int) or isinstance(maximum, bool) or maximum < 0:
raise ValueError(
"High-risk contextual-help baseline maximum must be a non-negative integer."
)
return payload
def _load_endpoint_declarations(
path: Path,
) -> dict[tuple[str, str, str], dict[str, Any]]: