12 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
zemion 64640327ae chore(release): pin datasource lifecycle governance
Dependency Audit / dependency-audit (push) Successful in 1m47s
Security Audit / security-audit (push) Failing after 11m49s
Deployment Installer / deployment-installer (push) Successful in 6s
Developer Meta-package Release / publish-package (push) Successful in 9s
2026-08-22 19:37:54 +02:00
zemion cc7c2a91ee chore(release): pin Records 0.1.20
Deployment Installer / deployment-installer (push) Successful in 5s
Dependency Audit / dependency-audit (push) Successful in 1m44s
Security Audit / security-audit (push) Failing after 11m51s
2026-08-22 18:42:15 +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 Every other active module requires a reviewed explanation of why it owns no
persistent subject-data store. Adding a migration invalidates that explanation. persistent subject-data store. Adding a migration invalidates that explanation.
- Active modules: 68 - Active modules: 72
- Registered and documented DSAR providers: 48 - Registered and documented DSAR providers: 48
- Reviewed no-store rationales: 20 - Reviewed no-store rationales: 24
- Unexplained coverage gaps: 0 - Unexplained coverage gaps: 0
| Module | Repository | Persistence | Coverage | Rationale | | 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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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` | `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. | | `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. | | `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. | | `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` | `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. | | `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 Provider search, export minimization, retention, and erasure behavior remains
documented and tested by each owning module. This matrix verifies adoption and 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] [project]
name = "govoplan" name = "govoplan"
version = "0.1.35" version = "0.1.39"
description = "Developer convenience package for a versioned GovOPlaN composition" description = "Developer convenience package for a versioned GovOPlaN composition"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
license = { text = "AGPL-3.0-or-later" } license = { text = "AGPL-3.0-or-later" }
dependencies = [ dependencies = [
"govoplan-core[server]==0.1.35", "govoplan-core[server]==0.1.39",
"govoplan-tenancy==0.1.18", "govoplan-tenancy==0.1.20",
"govoplan-organizations==0.1.18", "govoplan-organizations==0.1.20",
"govoplan-identity==0.1.18", "govoplan-identity==0.1.20",
"govoplan-idm==0.1.20", "govoplan-idm==0.1.24",
"govoplan-access==0.1.20", "govoplan-access==0.1.23",
"govoplan-admin==0.1.19", "govoplan-admin==0.1.22",
"govoplan-policy==0.1.20", "govoplan-policy==0.1.22",
"govoplan-audit==0.1.19", "govoplan-audit==0.1.20",
"govoplan-dashboard==0.1.18", "govoplan-dashboard==0.1.20",
"govoplan-files==0.1.20", "govoplan-files==0.1.23",
"govoplan-mail==0.1.22", "govoplan-mail==0.1.24",
"govoplan-campaign==0.1.24", "govoplan-campaign==0.1.27",
"govoplan-calendar==0.1.18", "govoplan-calendar==0.1.22",
"govoplan-docs==0.1.20", "govoplan-docs==0.1.22",
"govoplan-ops==0.1.19", "govoplan-ops==0.1.20",
] ]
[project.optional-dependencies] [project.optional-dependencies]
full = [ full = [
"govoplan-addresses==0.1.18", "govoplan-addresses==0.1.21",
"govoplan-approvals==0.1.18", "govoplan-approvals==0.1.20",
"govoplan-assets==0.1.19", "govoplan-assets==0.1.20",
"govoplan-booking==0.1.19", "govoplan-booking==0.1.20",
"govoplan-cases==0.1.20", "govoplan-cases==0.1.22",
"govoplan-certificates==0.1.19", "govoplan-certificates==0.1.20",
"govoplan-committee==0.1.18", "govoplan-committee==0.1.20",
"govoplan-connectors==0.1.22", "govoplan-connectors==0.1.25",
"govoplan-consultation==0.1.19", "govoplan-consultation==0.1.20",
"govoplan-contracts==0.1.19", "govoplan-contracts==0.1.20",
"govoplan-dataflow==0.1.20", "govoplan-dataflow==0.1.23",
"govoplan-datasources==0.1.20", "govoplan-datasources==0.1.24",
"govoplan-decisions==0.1.18", "govoplan-decisions==0.1.19",
"govoplan-dist-lists==0.1.18", "govoplan-dist-lists==0.1.20",
"govoplan-encryption==0.1.18", "govoplan-dms==0.1.20",
"govoplan-evaluation==0.1.19", "govoplan-encryption==0.1.19",
"govoplan-facilities==0.1.19", "govoplan-erp==0.1.20",
"govoplan-forms==0.1.20", "govoplan-evaluation==0.1.20",
"govoplan-forms-runtime==0.1.18", "govoplan-facilities==0.1.20",
"govoplan-grants==0.1.19", "govoplan-fit-connect==0.1.20",
"govoplan-helpdesk==0.1.20", "govoplan-forms==0.1.22",
"govoplan-identity-trust==0.1.18", "govoplan-forms-runtime==0.1.19",
"govoplan-inspections==0.1.19", "govoplan-grants==0.1.20",
"govoplan-learning==0.1.19", "govoplan-helpdesk==0.1.21",
"govoplan-mandates==0.1.18", "govoplan-identity-trust==0.1.20",
"govoplan-notifications==0.1.18", "govoplan-inspections==0.1.20",
"govoplan-parties==0.1.18", "govoplan-learning==0.1.20",
"govoplan-payments==0.1.20", "govoplan-mandates==0.1.19",
"govoplan-permits==0.1.19", "govoplan-notifications==0.1.19",
"govoplan-poll==0.1.19", "govoplan-parties==0.1.19",
"govoplan-portal==0.1.19", "govoplan-payments==0.1.21",
"govoplan-postbox==0.1.19", "govoplan-permits==0.1.20",
"govoplan-procurement==0.1.19", "govoplan-poll==0.1.20",
"govoplan-projects==0.1.18", "govoplan-portal==0.1.21",
"govoplan-quick-access==0.1.19", "govoplan-postbox==0.1.21",
"govoplan-records==0.1.19", "govoplan-procurement==0.1.20",
"govoplan-reporting==0.1.18", "govoplan-projects==0.1.19",
"govoplan-resources==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-rest==0.1.19",
"govoplan-risk-compliance==0.1.18", "govoplan-risk-compliance==0.1.20",
"govoplan-scheduling==0.1.18", "govoplan-scheduling==0.1.21",
"govoplan-search==0.1.18", "govoplan-search==0.1.19",
"govoplan-services==0.1.18", "govoplan-services==0.1.19",
"govoplan-soap==0.1.19", "govoplan-soap==0.1.19",
"govoplan-tasks==0.1.20", "govoplan-tasks==0.1.21",
"govoplan-templates==0.1.18", "govoplan-templates==0.1.21",
"govoplan-tickets==0.1.20", "govoplan-tickets==0.1.22",
"govoplan-transparency==0.1.19", "govoplan-transparency==0.1.20",
"govoplan-views==0.1.19", "govoplan-views==0.1.21",
"govoplan-voting==0.1.18", "govoplan-voting==0.1.20",
"govoplan-wiki==0.1.20", "govoplan-wiki==0.1.22",
"govoplan-workflow==0.1.21", "govoplan-workflow==0.1.22",
"govoplan-workflow-engine==0.1.19", "govoplan-workflow-engine==0.1.21",
"govoplan-xrechnung==0.1.21",
] ]
[project.urls] [project.urls]
+15 -15
View File
@@ -1,18 +1,18 @@
# Whole-product release install from immutable, independently versioned module tags. # Whole-product release install from immutable, independently versioned module tags.
# Only add a module after its referenced tag has been published. # Only add a module after its referenced tag has been published.
../govoplan-core[server] ../govoplan-core[server]
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.18 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.18 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.18 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.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.20 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.19 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.20 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.19 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.18 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.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.22 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.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.18 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.20 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.19 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} by_name = {item["name"]: item for item in selected}
self.assertIn("govoplan-core", by_name) self.assertIn("govoplan-core", by_name)
self.assertIn("govoplan-records", 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( payload = PACKAGE_SET.generate_package_set(
core_version=core_version, 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: def test_meta_package_resolves_to_exact_tagged_repository_targets(self) -> None:
targets = MODULE.package_targets() targets = MODULE.package_targets()
self.assertEqual(66, len(targets)) self.assertEqual(73, len(targets))
self.assertEqual(66, len({target.distribution for target in targets})) self.assertEqual(73, len({target.distribution for target in targets}))
by_name = {target.distribution: target 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.38", by_name["govoplan-core"].tag)
self.assertEqual("v0.1.8", by_name["govoplan-access"].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-core"].tag_exists)
self.assertTrue(by_name["govoplan-access"].has_webui) self.assertTrue(by_name["govoplan-access"].has_webui)
self.assertEqual( 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: def test_fastapi_route_scanner_includes_router_prefix(self) -> None:
tree = ast.parse( 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.", "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.", "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.", "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.", "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.", "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.", "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.", "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.", "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.", "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.", "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.", "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", "helperText",
"helpText" "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 actionComponentPattern = /(?:Action|Button|Link)$/;
const contributionTypes = new Map([ const contributionTypes = new Map([
["AdminSectionsUiCapability", "admin_section"], ["AdminSectionsUiCapability", "admin_section"],
@@ -200,20 +222,43 @@ function inspectSource(repository, sourceRoot, sourcePath) {
const parentAttributes = parentFormField const parentAttributes = parentFormField
? jsxAttributes(parentFormField) ? jsxAttributes(parentFormField)
: new Map(); : new Map();
const scopedAncestorAttributes = nearestScopedHelpAttributes(node);
const label = const label =
attributes.get("label") ?? attributes.get("label") ??
attributes.get("aria-label") ?? attributes.get("aria-label") ??
parentAttributes.get("label") ?? parentAttributes.get("label") ??
null; null;
const help = firstAttribute(attributes, helpAttributes) ?? const help = firstAttribute(attributes, helpAttributes) ??
firstAttribute(parentAttributes, helpAttributes); firstAttribute(parentAttributes, helpAttributes) ??
firstAttribute(scopedAncestorAttributes, helpAttributes);
const hasHelp = hasAnyAttribute(attributes, 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( const explicitId = firstAttribute(
attributes, attributes,
new Set(["interfaceId", "data-interface-id", "id", "name", "field"]) new Set(["interfaceId", "data-interface-id", "id", "name", "field"])
); );
const context = nearestNamedContext(node); 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( const stableId = sourceIdentity(
"field", "field",
node, node,
@@ -237,7 +282,14 @@ function inspectSource(repository, sourceRoot, sourcePath) {
help: help ?? null, help: help ?? null,
helpId: hasHelp ? `${stableId}.help` : null, helpId: hasHelp ? `${stableId}.help` : null,
helpDynamic: hasHelp && 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"]) new Set(["interfaceId", "data-interface-id", "id", "name"])
); );
const context = nearestNamedContext(node); 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({ result.actions.push({
...locate(node), ...locate(node),
id: sourceIdentity( id: sourceIdentity(
@@ -273,7 +338,15 @@ function inspectSource(repository, sourceRoot, sourcePath) {
idSource: explicitId === null ? "source_anchor" : "explicit", idSource: explicitId === null ? "source_anchor" : "explicit",
context, context,
component, 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; 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) { function jsxAttributes(node) {
const mapped = new Map(); const mapped = new Map();
for (const attribute of node.attributes.properties) { for (const attribute of node.attributes.properties) {
@@ -579,6 +672,34 @@ function hasAnyAttribute(attributes, names) {
return false; 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) { function slug(value) {
const normalized = value const normalized = value
.toLowerCase() .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 = ( DEFAULT_ENDPOINT_DECLARATIONS = (
META_ROOT / "tools" / "inventory" / "endpoint-surface-declarations.json" 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") REQUIRED_LOCALES = ("de", "en")
REFERENCE_LOCALE = "de" REFERENCE_LOCALE = "de"
@@ -75,6 +78,12 @@ def main() -> int:
default=DEFAULT_ENDPOINT_DECLARATIONS, default=DEFAULT_ENDPOINT_DECLARATIONS,
help="Versioned endpoint-surface declaration registry.", 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() 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"))
@@ -85,11 +94,15 @@ def main() -> int:
endpoint_declarations = _load_endpoint_declarations( endpoint_declarations = _load_endpoint_declarations(
args.endpoint_declarations.resolve() args.endpoint_declarations.resolve()
) )
high_risk_help_baseline = _load_high_risk_help_baseline(
args.high_risk_help_baseline.resolve()
)
inventory = _assemble_inventory( inventory = _assemble_inventory(
webui=webui, webui=webui,
backend_endpoints=backend_endpoints, backend_endpoints=backend_endpoints,
manifests=manifests, manifests=manifests,
endpoint_declarations=endpoint_declarations, endpoint_declarations=endpoint_declarations,
high_risk_help_baseline=high_risk_help_baseline,
runtime_snapshot=( runtime_snapshot=(
_load_runtime_snapshot(args.runtime_snapshot.resolve()) _load_runtime_snapshot(args.runtime_snapshot.resolve())
if args.runtime_snapshot is not None if args.runtime_snapshot is not None
@@ -164,6 +177,28 @@ def _strict_failures(
f"{len(declaration_health['stale_runtime_routes'])} runtime route " f"{len(declaration_health['stale_runtime_routes'])} runtime route "
"declarations have no WebUI implementation" "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") runtime_comparison = inventory.get("runtime_comparison")
if ( if (
check_declarations check_declarations
@@ -355,6 +390,29 @@ def _extract_manifests(
} }
for permission in manifest.permissions 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": ( "architecture": (
manifest.architecture.to_dict() manifest.architecture.to_dict()
if manifest.architecture is not None if manifest.architecture is not None
@@ -400,6 +458,7 @@ def _assemble_inventory(
backend_endpoints: list[dict[str, Any]], backend_endpoints: list[dict[str, Any]],
manifests: list[dict[str, Any]], manifests: list[dict[str, Any]],
endpoint_declarations: dict[tuple[str, str, str], 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, runtime_snapshot: dict[str, Any] | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
frontend_refs = webui["frontendApiReferences"] 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) if any(key not in catalog_keys.get(locale, set()) for locale in expected_locales)
] ]
fields = webui["fields"] fields = webui["fields"]
actions = webui.get("actions", [])
help_candidates = [field for field in fields if field["helpCandidate"]] help_candidates = [field for field in fields if field["helpCandidate"]]
dynamic_help = [field for field in fields if field.get("helpDynamic")] 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( governance_adoption = Counter(
dimension["adoption"] dimension["adoption"]
for manifest in manifests for manifest in manifests
@@ -499,10 +597,34 @@ def _assemble_inventory(
"modules": manifests, "modules": manifests,
"interface_declarations": source_declarations, "interface_declarations": source_declarations,
"declaration_health": declaration_health, "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, "runtime_comparison": runtime_comparison,
"ui": { "ui": {
"fields": fields, "fields": fields,
"actions": webui.get("actions", []), "actions": actions,
"labels": webui["labels"], "labels": webui["labels"],
"visible_text": webui["visibleText"], "visible_text": webui["visibleText"],
"routes": webui["routes"], "routes": webui["routes"],
@@ -554,7 +676,19 @@ def _assemble_inventory(
"ui_fields_with_resolvable_f1_context": len(fields), "ui_fields_with_resolvable_f1_context": len(fields),
"help_review_candidates": len(help_candidates), "help_review_candidates": len(help_candidates),
"dynamic_help_references": len(dynamic_help), "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), "interface_declarations": len(source_declarations),
"duplicate_interface_ids": len(declaration_health["duplicate_ids"]), "duplicate_interface_ids": len(declaration_health["duplicate_ids"]),
"undeclared_source_surfaces": len( "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"] for item in inventory["api"]["unreferenced_by_static_webui_scan"]
) )
classification_counts = inventory["api"]["classification_counts"] 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 = [ lines = [
"# GovOPlaN Platform Interface Inventory", "# 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 a resolvable F1 context: {summary['ui_fields_with_resolvable_f1_context']}",
f"- Fields with dynamic help references: {summary['dynamic_help_references']}", f"- Fields with dynamic help references: {summary['dynamic_help_references']}",
f"- Help review candidates: {summary['help_review_candidates']}", 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"- Stable interface declarations: {summary['interface_declarations']}",
f"- Duplicate interface IDs: {summary['duplicate_interface_ids']}", f"- Duplicate interface IDs: {summary['duplicate_interface_ids']}",
f"- WebUI surfaces missing runtime declarations: {summary['undeclared_source_surfaces']}", 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} |" f"| `{repository}` | {count} |"
for repository, count in sorted(help_by_repository.items()) 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( 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( def _load_endpoint_declarations(
path: Path, path: Path,
) -> dict[tuple[str, str, str], dict[str, Any]]: ) -> dict[tuple[str, str, str], dict[str, Any]]: