27 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
zemion 7c92565d9d Compose v0.1.35 resident permit configuration package
Dependency Audit / dependency-audit (push) Successful in 1m44s
Developer Meta-package Release / publish-package (push) Successful in 9s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m59s
2026-08-22 18:05:58 +02:00
zemion 23bfe5e2f8 Compose Core v0.1.34 and Mail v0.1.22
Dependency Audit / dependency-audit (push) Successful in 1m46s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m54s
Developer Meta-package Release / publish-package (push) Successful in 9s
2026-08-22 17:30:27 +02:00
zemion cf2f7f6890 Compose Core v0.1.33 and Connectors v0.1.22
Dependency Audit / dependency-audit (push) Successful in 1m44s
Deployment Installer / deployment-installer (push) Successful in 7s
Security Audit / security-audit (push) Failing after 12m8s
2026-08-22 16:31:58 +02:00
zemion 23b601bc0d build: compose external knowledge connector slice
Security Audit / security-audit (push) Failing after 12m23s
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
2026-08-22 14:44:55 +02:00
zemion 99c52c2153 build: compose governed Wiki vertical slice
Dependency Audit / dependency-audit (push) Successful in 1m44s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 12m20s
2026-08-22 13:33:31 +02:00
zemion ca68d98806 build: release ticket operations vertical slice
Dependency Audit / dependency-audit (push) Successful in 1m40s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m44s
2026-08-22 12:24:55 +02:00
zemion 79c4cb067a build: release localized documentation batch
Dependency Audit / dependency-audit (push) Successful in 1m39s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m41s
2026-08-22 08:45:51 +02:00
zemion cd498dc1d8 build: release seed documentation coverage batch
Dependency Audit / dependency-audit (push) Successful in 1m40s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m35s
2026-08-22 08:12:04 +02:00
zemion e07b3487e3 build: release documentation coverage batch
Dependency Audit / dependency-audit (push) Successful in 1m41s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m23s
2026-08-22 07:40:10 +02:00
zemion 72279de2c0 chore: sync Tasks and Postbox documentation releases
Dependency Audit / dependency-audit (push) Successful in 1m42s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m43s
2026-08-22 07:07:49 +02:00
zemion 88543ab115 chore: sync REST and SOAP reference releases
Dependency Audit / dependency-audit (push) Successful in 1m40s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m18s
2026-08-22 06:38:20 +02:00
zemion 81fe0f4680 chore: sync Docs German reference release
Dependency Audit / dependency-audit (push) Successful in 1m36s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m3s
2026-08-22 06:12:49 +02:00
zemion fe784cc562 chore: sync Mail German help release
Dependency Audit / dependency-audit (push) Successful in 1m36s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 11m0s
2026-08-22 05:45:12 +02:00
zemion 9fe7ad2cb4 chore: sync Mail POP3 legacy import release
Dependency Audit / dependency-audit (push) Successful in 1m31s
Deployment Installer / deployment-installer (push) Successful in 5s
Security Audit / security-audit (push) Failing after 11m3s
2026-08-22 05:09:01 +02:00
zemion f1eebd849c chore: sync Campaign portable transfer release
Dependency Audit / dependency-audit (push) Successful in 1m35s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Failing after 10m52s
2026-08-22 03:58:31 +02:00
16 changed files with 1152 additions and 116 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: 45 - Registered and documented DSAR providers: 48
- Reviewed no-store rationales: 23 - Reviewed no-store rationales: 24
- Unexplained coverage gaps: 0 - Unexplained coverage gaps: 0
| Module | Repository | Persistence | Coverage | Rationale | | Module | Repository | Persistence | Coverage | Rationale |
@@ -32,15 +32,18 @@ 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. |
| `helpdesk` | `govoplan-helpdesk` | No module migration | Reviewed no-store rationale | Contract-only module: helpdesk requests, queues, and resolutions are not persisted; reassess before adding a migration-owned store. | | `helpdesk` | `govoplan-helpdesk` | Migration-owned | Provider | Provider `privacy.dsar.helpdesk` is registered and documented. |
| `identity` | `govoplan-identity` | Migration-owned | Provider | Provider `privacy.dsar.identity` is registered and documented. | | `identity` | `govoplan-identity` | Migration-owned | Provider | Provider `privacy.dsar.identity` is registered and documented. |
| `identity_trust` | `govoplan-identity-trust` | Migration-owned | Provider | Provider `privacy.dsar.identity_trust` is registered and documented. | | `identity_trust` | `govoplan-identity-trust` | Migration-owned | Provider | Provider `privacy.dsar.identity_trust` is registered and documented. |
| `idm` | `govoplan-idm` | Migration-owned | Provider | Provider `privacy.dsar.idm` is registered and documented. | | `idm` | `govoplan-idm` | Migration-owned | Provider | Provider `privacy.dsar.idm` is registered and documented. |
@@ -73,13 +76,14 @@ persistent subject-data store. Adding a migration invalidates that explanation.
| `tasks` | `govoplan-tasks` | Migration-owned | Provider | Provider `privacy.dsar.tasks` is registered and documented. | | `tasks` | `govoplan-tasks` | Migration-owned | Provider | Provider `privacy.dsar.tasks` is registered and documented. |
| `templates` | `govoplan-templates` | Migration-owned | Provider | Provider `privacy.dsar.templates` is registered and documented. | | `templates` | `govoplan-templates` | Migration-owned | Provider | Provider `privacy.dsar.templates` is registered and documented. |
| `tenancy` | `govoplan-tenancy` | No module migration | Reviewed no-store rationale | Orchestration module: tenant lifecycle and settings use Core-owned storage; Access covers account and membership subject data. | | `tenancy` | `govoplan-tenancy` | No module migration | Reviewed no-store rationale | Orchestration module: tenant lifecycle and settings use Core-owned storage; Access covers account and membership subject data. |
| `tickets` | `govoplan-tickets` | No module migration | Reviewed no-store rationale | Contract-only module: ticket, queue, SLA, and adapter persistence are not implemented; reassess before adding a migration-owned store. | | `tickets` | `govoplan-tickets` | Migration-owned | Provider | Provider `privacy.dsar.tickets` is registered and documented. |
| `transparency` | `govoplan-transparency` | No module migration | Reviewed no-store rationale | Contract-only module: requests, disclosure reviews, and publications are not persisted; reassess before adding a migration-owned store. | | `transparency` | `govoplan-transparency` | No module migration | Reviewed no-store rationale | Contract-only module: requests, disclosure reviews, and publications are not persisted; reassess before adding a migration-owned store. |
| `views` | `govoplan-views` | Migration-owned | Provider | Provider `privacy.dsar.views` is registered and documented. | | `views` | `govoplan-views` | Migration-owned | Provider | Provider `privacy.dsar.views` is registered and documented. |
| `voting` | `govoplan-voting` | Migration-owned | Provider | Provider `privacy.dsar.voting` is registered and documented. | | `voting` | `govoplan-voting` | Migration-owned | Provider | Provider `privacy.dsar.voting` is registered and documented. |
| `wiki` | `govoplan-wiki` | No module migration | Reviewed no-store rationale | Contract-only module: wiki spaces, pages, and revisions are not persisted; reassess before adding a migration-owned store. | | `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
@@ -109,7 +109,9 @@ semantics as authenticated navigation routes.
| `/scheduling/public/:requestId/:token` | Scheduling | Public signed token | Public participation | Scheduling #8 complete in `c17cbda` | | `/scheduling/public/:requestId/:token` | Scheduling | Public signed token | Public participation | Scheduling #8 complete in `c17cbda` |
| `/search` | Search | `search:result:read` | Keyboard-first global/context overlay and full results fallback | Search pattern migration complete in [Search #4](https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/4); durable evidence in `govoplan-search/docs/INTERFACE_PATTERN_MIGRATION.md` | | `/search` | Search | `search:result:read` | Keyboard-first global/context overlay and full results fallback | Search pattern migration complete in [Search #4](https://git.add-ideas.de/GovOPlaN/govoplan-search/issues/4); durable evidence in `govoplan-search/docs/INTERFACE_PATTERN_MIGRATION.md` |
| `/templates` | Templates | Template read/write/publish/render/admin | Governed library, immutable-revision editor, compatibility preview, and render evidence | Templates pattern migration complete in [Templates #5](https://git.add-ideas.de/GovOPlaN/govoplan-templates/issues/5), commit `72fafa2` | | `/templates` | Templates | Template read/write/publish/render/admin | Governed library, immutable-revision editor, compatibility preview, and render evidence | Templates pattern migration complete in [Templates #5](https://git.add-ideas.de/GovOPlaN/govoplan-templates/issues/5), commit `72fafa2` |
| `/tickets` | Tickets | `tickets:ticket:read` | Governed operational queue/detail workspace with distinct report, triage, assignment, resolution, comment, reference and removal boundaries | Tickets vertical slice and pattern migration complete in [Tickets #1](https://git.add-ideas.de/GovOPlaN/govoplan-tickets/issues/1), release `v0.1.20` |
| `/voting` | Voting | `voting:ballot:read` | Governed ballot workspace | Voting pattern migration complete in [Voting #1](https://git.add-ideas.de/GovOPlaN/govoplan-voting/issues/1), commit `2625990` | | `/voting` | Voting | `voting:ballot:read` | Governed ballot workspace | Voting pattern migration complete in [Voting #1](https://git.add-ideas.de/GovOPlaN/govoplan-voting/issues/1), commit `2625990` |
| `/wiki` | Wiki | `wiki:page:read` | Governed space-tree/page workspace with draft editing, immutable revision comparison, publication, comments, typed references and archival | Native Wiki vertical slice and pattern migration implemented in [Wiki #1](https://git.add-ideas.de/GovOPlaN/govoplan-wiki/issues/1), release `v0.1.20` |
| `/workflow` | Workflow | Definition read or instance admin | Native BPMN editor, governed revision actions and execution evidence | Workflow pattern migration complete in [Workflow #15](https://git.add-ideas.de/GovOPlaN/govoplan-workflow/issues/15); durable evidence in `govoplan-workflow/docs/INTERFACE_PATTERN_MIGRATION.md` | | `/workflow` | Workflow | Definition read or instance admin | Native BPMN editor, governed revision actions and execution evidence | Workflow pattern migration complete in [Workflow #15](https://git.add-ideas.de/GovOPlaN/govoplan-workflow/issues/15); durable evidence in `govoplan-workflow/docs/INTERFACE_PATTERN_MIGRATION.md` |
## Final Module Closure Evidence ## Final Module Closure Evidence
@@ -323,8 +325,8 @@ The generated manifest snapshot reports no WebUI package for:
`govoplan-learning`, `govoplan-mandates`, `govoplan-parties`, `govoplan-learning`, `govoplan-mandates`, `govoplan-parties`,
`govoplan-permits`, `govoplan-poll`, `govoplan-procurement`, `govoplan-permits`, `govoplan-poll`, `govoplan-procurement`,
`govoplan-records`, `govoplan-resources`, `govoplan-rest`, `govoplan-records`, `govoplan-resources`, `govoplan-rest`,
`govoplan-services`, `govoplan-soap`, `govoplan-tickets`, `govoplan-services`, `govoplan-soap`, `govoplan-transparency`, and
`govoplan-transparency`, `govoplan-wiki`, and `govoplan-workflow-engine`. `govoplan-workflow-engine`.
Tenancy does provide composed administration surfaces despite having no direct Tenancy does provide composed administration surfaces despite having no direct
route. This section is only negative package evidence; connector-only, route. This section is only negative package evidence; connector-only,
+74 -70
View File
@@ -4,85 +4,89 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan" name = "govoplan"
version = "0.1.29" 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.29", "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.19", "govoplan-access==0.1.23",
"govoplan-admin==0.1.18", "govoplan-admin==0.1.22",
"govoplan-policy==0.1.20", "govoplan-policy==0.1.22",
"govoplan-audit==0.1.18", "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.18", "govoplan-mail==0.1.24",
"govoplan-campaign==0.1.23", "govoplan-campaign==0.1.27",
"govoplan-calendar==0.1.18", "govoplan-calendar==0.1.22",
"govoplan-docs==0.1.18", "govoplan-docs==0.1.22",
"govoplan-ops==0.1.18", "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.18", "govoplan-assets==0.1.20",
"govoplan-booking==0.1.18", "govoplan-booking==0.1.20",
"govoplan-cases==0.1.19", "govoplan-cases==0.1.22",
"govoplan-certificates==0.1.18", "govoplan-certificates==0.1.20",
"govoplan-committee==0.1.18", "govoplan-committee==0.1.20",
"govoplan-connectors==0.1.20", "govoplan-connectors==0.1.25",
"govoplan-consultation==0.1.18", "govoplan-consultation==0.1.20",
"govoplan-contracts==0.1.18", "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.18", "govoplan-encryption==0.1.19",
"govoplan-facilities==0.1.18", "govoplan-erp==0.1.20",
"govoplan-forms==0.1.19", "govoplan-evaluation==0.1.20",
"govoplan-forms-runtime==0.1.18", "govoplan-facilities==0.1.20",
"govoplan-grants==0.1.18", "govoplan-fit-connect==0.1.20",
"govoplan-helpdesk==0.1.18", "govoplan-forms==0.1.22",
"govoplan-identity-trust==0.1.18", "govoplan-forms-runtime==0.1.19",
"govoplan-inspections==0.1.18", "govoplan-grants==0.1.20",
"govoplan-learning==0.1.18", "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.18", "govoplan-notifications==0.1.19",
"govoplan-poll==0.1.18", "govoplan-parties==0.1.19",
"govoplan-portal==0.1.18", "govoplan-payments==0.1.21",
"govoplan-postbox==0.1.18", "govoplan-permits==0.1.20",
"govoplan-procurement==0.1.18", "govoplan-poll==0.1.20",
"govoplan-projects==0.1.18", "govoplan-portal==0.1.21",
"govoplan-quick-access==0.1.18", "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.18", "govoplan-quick-access==0.1.20",
"govoplan-rest==0.1.18", "govoplan-records==0.1.22",
"govoplan-risk-compliance==0.1.18", "govoplan-reporting==0.1.20",
"govoplan-scheduling==0.1.18", "govoplan-resources==0.1.20",
"govoplan-search==0.1.18", "govoplan-rest==0.1.19",
"govoplan-services==0.1.18", "govoplan-risk-compliance==0.1.20",
"govoplan-soap==0.1.18", "govoplan-scheduling==0.1.21",
"govoplan-tasks==0.1.19", "govoplan-search==0.1.19",
"govoplan-templates==0.1.18", "govoplan-services==0.1.19",
"govoplan-tickets==0.1.18", "govoplan-soap==0.1.19",
"govoplan-transparency==0.1.18", "govoplan-tasks==0.1.21",
"govoplan-views==0.1.18", "govoplan-templates==0.1.21",
"govoplan-voting==0.1.18", "govoplan-tickets==0.1.22",
"govoplan-wiki==0.1.18", "govoplan-transparency==0.1.20",
"govoplan-workflow==0.1.20", "govoplan-views==0.1.21",
"govoplan-workflow-engine==0.1.19", "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] [project.urls]
@@ -17,6 +17,26 @@ human review handoff, formal outcome, Postbox delivery channel, and Records
filing/retention target. Generic permit wording is no longer acceptance filing/retention target. Generic permit wording is no longer acceptance
evidence for this package. evidence for this package.
The package is now executable rather than metadata-only. Its Access fragments
create the bounded resident-permit clerk role, collect only the tenant-local
responsibility group key and name, create that group, and bind the role. The
Forms-owned fragment carries a digest-bound German-reference application schema
and imports it as a tenant-local draft with source provenance. Reapplying the
same source digest is a no-op; replacing an unrelated local definition remains
blocked unless the reviewed package explicitly selects a new revision. Normal
Forms review and publication are still required before the definition can serve
new applications. The Workflow Engine-owned fragment materializes and activates
the tenant review baseline, resolves the chosen responsibility group into each
human handoff, and preserves the evidence, decision, and EUR 30 payment-review
steps as a replay-safe contributed definition.
Services, Cases, Payments, Tasks, and the optional delivery and Records modules
already execute the pinned journey through their runtime
contracts, but their reusable configuration fragments are not yet claimed by
this package. Until those module-owned configuration providers are added, the
package preflight deliberately distinguishes the installed runtime composition
from the Access, Forms, and Workflow configurations it can currently materialize.
An installed Forms and Forms Runtime pair adds an alternative governed entry An installed Forms and Forms Runtime pair adds an alternative governed entry
path before case/workflow handoff: path before case/workflow handoff:
@@ -76,3 +96,11 @@ and Decision revision filing. Target-environment browser accessibility,
production identity and delivery, a named archive profile, and recovery evidence production identity and delivery, a named archive profile, and recovery evidence
are still required before this product package may claim `reference_ready` are still required before this product package may claim `reference_ready`
maturity. maturity.
The generic package orchestrator stops at the first provider apply or health
blocker. Access and Forms may commit in separate provider transactions, so the
operator must retain the reviewed pre-apply database snapshot until verification
is complete. The Admin result reports no-op, snapshot-required, or partial-apply
recovery state and never describes this as atomic cross-module undo. Exported
fragments carry source/module/operator/scope provenance; supplied values and
credentials are not serialized into that provenance.
@@ -8,36 +8,366 @@
"category": "institutional-governance", "category": "institutional-governance",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"required_modules": [ "required_modules": [
{"module_id": "access"},
{"module_id": "audit"}, {"module_id": "audit"},
{"module_id": "cases"}, {"module_id": "cases"},
{"module_id": "decisions"}, {"module_id": "decisions"},
{"module_id": "forms"},
{"module_id": "forms_runtime"},
{"module_id": "mandates"}, {"module_id": "mandates"},
{"module_id": "parties"}, {"module_id": "parties"},
{"module_id": "payments"},
{"module_id": "policy"}, {"module_id": "policy"},
{"module_id": "portal"}, {"module_id": "portal"},
{"module_id": "services"} {"module_id": "services"},
{"module_id": "tasks"},
{"module_id": "workflow_engine"}
], ],
"required_capabilities": [ "required_capabilities": [
"access.configuration",
"cases.party_context", "cases.party_context",
"cases.service_intake", "cases.service_intake",
"decisions.registry", "decisions.registry",
"forms.configuration",
"forms.definitions",
"mandates.resolver", "mandates.resolver",
"parties.resolver", "parties.resolver",
"payments.requests",
"portal.service_directory", "portal.service_directory",
"services.availability", "services.availability",
"services.definitions" "services.definitions",
"workflow.configuration"
], ],
"optional_modules": [ "optional_modules": [
{"module_id": "approvals"}, {"module_id": "approvals"},
{"module_id": "committee"}, {"module_id": "committee"},
{"module_id": "files"}, {"module_id": "files"},
{"module_id": "forms"},
{"module_id": "forms_runtime"},
{"module_id": "postbox"}, {"module_id": "postbox"},
{"module_id": "records"}, {"module_id": "records"},
{"module_id": "search"}, {"module_id": "search"}
{"module_id": "tasks"}, ],
{"module_id": "workflow_engine"} "data_requirements": [
{
"key": "responsible_group_slug",
"label": "Responsible permit group key",
"data_type": "string",
"required": true,
"secret": false,
"description": "Tenant-local stable key for the group that reviews resident parking permit applications."
},
{
"key": "responsible_group_name",
"label": "Responsible permit group name",
"data_type": "string",
"required": true,
"secret": false,
"description": "Human-readable tenant-local name shown for the responsible permit group."
}
],
"fragments": [
{
"module_id": "access",
"fragment_type": "roles",
"fragment_id": "resident-parking-permit-clerk",
"payload": {
"items": [
{
"slug": "resident-parking-permit-clerk",
"name": "Resident parking permit clerk",
"description": "Reviews resident parking permit submissions, workflow handoffs, cases, decisions, and payment evidence.",
"permissions": [
"cases:case:read",
"cases:case:create",
"cases:case:update",
"decisions:decision:read",
"decisions:decision:write",
"forms:definition:read",
"forms_runtime:workspace:read",
"forms_runtime:workspace:write",
"payments:payment:read",
"payments:payment:write",
"tasks:item:read",
"tasks:item:write",
"workflow:definition:read",
"workflow:instance:read",
"workflow:instance:start",
"workflow:instance:transition"
]
}
]
}
},
{
"module_id": "access",
"fragment_type": "groups",
"fragment_id": "resident-parking-permit-responsibility",
"payload": {
"items": [
{
"slug": {"$data": "responsible_group_slug"},
"name": {"$data": "responsible_group_name"},
"description": "Tenant-local responsibility group for the resident parking permit reference journey."
}
]
}
},
{
"module_id": "access",
"fragment_type": "group_role_assignments",
"fragment_id": "resident-parking-permit-clerk-assignment",
"payload": {
"items": [
{
"group": {"$data": "responsible_group_slug"},
"role": "resident-parking-permit-clerk"
}
]
}
},
{
"module_id": "forms",
"fragment_type": "definition",
"fragment_id": "resident-parking-permit-application",
"payload": {
"on_conflict": "new_revision",
"change_reason": "Install the reviewed resident parking permit reference form.",
"fragment": {
"kind": "govoplan.forms.definition",
"contract_version": "0.1.0",
"definition": {
"reference": {
"kind": "form",
"owner_module": "forms",
"object_id": "resident-parking-permit-application",
"tenant_id": "reference-package",
"version": "3",
"valid_at": null,
"label": null
},
"key": "resident-parking-permit-application",
"temporal": {
"revision": "3",
"valid_from": null,
"valid_to": null,
"recorded_at": "2026-08-22T00:00:00+00:00",
"superseded_at": null,
"change_reason": "Reference package revision."
},
"title": "Resident parking permit",
"description": "Apply for a resident parking permit through a digital or assisted channel.",
"fields": [
{
"key": "applicant_name",
"label": "Name",
"value_type": "text",
"required": true,
"help_text": null,
"options": [],
"constraints": {"min_length": 2, "max_length": 200},
"default_value": null
},
{
"key": "applicant_email",
"label": "Email",
"value_type": "text",
"required": true,
"help_text": null,
"options": [],
"constraints": {"format": "email"},
"default_value": null
},
{
"key": "residence_address",
"label": "Primary residence",
"value_type": "text",
"required": true,
"help_text": null,
"options": [],
"constraints": {"max_length": 500},
"default_value": null
},
{
"key": "licence_plate",
"label": "Licence plate",
"value_type": "text",
"required": true,
"help_text": null,
"options": [],
"constraints": {"max_length": 20},
"default_value": null
}
],
"publication_state": "published",
"allow_drafts": true,
"max_attachments": 4,
"signature_requirement": "none",
"policy_refs": [
"law:resident-parking-permit",
"records:resident-parking-permit"
],
"handoff_kinds": ["case", "workflow"],
"metadata": {},
"pages": [
{
"key": "application",
"title": "Application",
"description": null,
"sections": [
{
"key": "applicant-and-vehicle",
"title": "Applicant and vehicle",
"description": null,
"field_keys": [
"applicant_name",
"applicant_email",
"residence_address",
"licence_plate"
]
}
]
}
],
"localizations": [
{
"locale": "de",
"title": "Anwohnerparkausweis beantragen",
"description": "Einen Anwohnerparkausweis digital oder mit Unterstützung beantragen.",
"field_labels": {
"applicant_name": "Name",
"applicant_email": "E-Mail-Adresse",
"residence_address": "Hauptwohnsitz",
"licence_plate": "Kennzeichen"
},
"field_help_texts": {},
"option_labels": {},
"page_titles": {"application": "Antrag"},
"section_titles": {
"applicant-and-vehicle": "Antragstellende Person und Fahrzeug"
}
}
],
"fallback_locale": "de"
},
"definition_sha256": "7dc108002d532c07e5e7f3b14029a9d4deb3836ebb65d97fb6b51166a70e0ed4",
"provenance": {
"owner_module": "forms",
"tenant_id": "reference-package",
"form_id": "resident-parking-permit-application",
"revision": "3",
"exported_at": "2026-08-22T12:00:00+00:00",
"exported_by": "GovOPlaN reference package"
}
}
}
},
{
"module_id": "workflow_engine",
"fragment_type": "workflow_definitions",
"fragment_id": "resident-parking-permit-workflow",
"payload": {
"schema_version": 1,
"origin_module_id": "configuration_package.service_to_decision",
"origin_module_version": "0.1.0",
"items": [
{
"definition_key": "resident-parking-permit-review",
"name": "Resident parking permit review",
"description": "Review evidence, record the formal decision, and verify payment evidence for the resident parking permit reference journey.",
"scope_type": "tenant",
"allow_start": true,
"allow_reuse": true,
"allow_automation": false,
"execution_mode": "guided",
"activate_on_install": true,
"graph": {
"schema_version": 1,
"nodes": [
{
"id": "start",
"type": "workflow.start.manual",
"label": "Application received",
"config": {"input_schema_ref": "form:resident-parking-permit-application"}
},
{
"id": "review-evidence",
"type": "workflow.review",
"label": "Review application evidence",
"config": {
"title": "Review resident parking permit evidence",
"reviewer": {
"kind": "group",
"id": {"$data": "responsible_group_slug"},
"label": {"$data": "responsible_group_name"}
},
"due_after": "P14D",
"required_evidence": [
"identity",
"primary_residence",
"vehicle_registration"
],
"view_surface_ids": []
}
},
{
"id": "record-decision",
"type": "workflow.activity",
"label": "Record formal decision",
"config": {
"title": "Record the resident parking permit decision",
"instructions": "Record the operative result, reasoning, legal basis, remedy, and exact evidence references through the Decisions capability.",
"assignee": {
"kind": "group",
"id": {"$data": "responsible_group_slug"},
"label": {"$data": "responsible_group_name"}
},
"due_after": "P7D",
"view_surface_ids": []
}
},
{
"id": "verify-payment",
"type": "workflow.activity",
"label": "Verify payment evidence",
"config": {
"title": "Verify the resident parking permit fee",
"instructions": "Verify the EUR 30.00 obligation, immutable receipt evidence, currency, amount, and transaction reference before completion.",
"assignee": {
"kind": "group",
"id": {"$data": "responsible_group_slug"},
"label": {"$data": "responsible_group_name"}
},
"due_after": "P14D",
"view_surface_ids": []
}
},
{
"id": "completed",
"type": "workflow.end.completed",
"label": "Permit journey complete",
"config": {"output_mapping": {}}
}
],
"edges": [
{"id": "start-review", "source": "start", "target": "review-evidence"},
{"id": "review-decision", "source": "review-evidence", "source_port": "approved", "target": "record-decision"},
{"id": "decision-payment", "source": "record-decision", "target": "verify-payment"},
{"id": "payment-completed", "source": "verify-payment", "target": "completed"}
],
"metadata": {
"reference_journey": "resident-parking-permit",
"locale": "de-DE",
"payment_amount_minor": 3000,
"payment_currency": "EUR"
}
},
"metadata": {
"reference_package": "product.service-to-decision",
"form_id": "resident-parking-permit-application"
}
}
]
}
}
], ],
"evidence": [ "evidence": [
{ {
+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.19 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.18 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.18 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.18 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.23 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.18 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.18 govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.20
+167 -1
View File
@@ -5,9 +5,17 @@ import json
from pathlib import Path from pathlib import Path
import unittest import unittest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from govoplan_core.core.configuration_packages import ( from govoplan_core.core.configuration_packages import (
ConfigurationApplyResult,
ConfigurationExportResult,
ConfigurationPackageManifest, ConfigurationPackageManifest,
ConfigurationPlanItem,
ConfigurationPreflightContext, ConfigurationPreflightContext,
ConfigurationPreflightResult,
ConfigurationProviderDescription,
configuration_package_claim_issues, configuration_package_claim_issues,
dry_run_configuration_package, dry_run_configuration_package,
) )
@@ -54,10 +62,19 @@ class ConfigurationPackageArtifactTests(unittest.TestCase):
f"Missing repository for {requirement.module_id}", f"Missing repository for {requirement.module_id}",
) )
provider_module_ids = tuple(
sorted({fragment.module_id for fragment in manifest.fragments})
)
providers = tuple(_ArtifactProvider(module_id) for module_id in provider_module_ids)
supplied_data = {
str(item["key"]): _sample_value(item)
for item in manifest.data_requirements
}
result = dry_run_configuration_package( result = dry_run_configuration_package(
manifest, manifest,
(), providers,
ConfigurationPreflightContext( ConfigurationPreflightContext(
supplied_data=supplied_data,
installed_modules={ installed_modules={
item.module_id: item.version or "workspace" item.module_id: item.version or "workspace"
for item in manifest.required_modules for item in manifest.required_modules
@@ -74,6 +91,155 @@ class ConfigurationPackageArtifactTests(unittest.TestCase):
self.assertIn("product.governed-data-assurance", package_ids) self.assertIn("product.governed-data-assurance", package_ids)
self.assertIn("product.service-to-decision", package_ids) self.assertIn("product.service-to-decision", package_ids)
def test_service_to_decision_package_imports_its_form_as_an_idempotent_local_draft(self) -> None:
from govoplan_forms.backend.configuration_provider import (
_apply_definition,
_preflight_definition,
)
from govoplan_forms.backend.db.models import FormDefinitionRevision
from govoplan_forms.backend.service import get_form_definition
package = ConfigurationPackageManifest.from_mapping(json.loads(
(PACKAGE_ROOT / "product/service-to-decision/package.json").read_text(
encoding="utf-8"
)
))
fragment = next(
item
for item in package.fragments
if item.module_id == "forms" and item.fragment_type == "definition"
)
context = ConfigurationPreflightContext(
tenant_id="tenant-reference-test",
operator_user_id="operator-1",
operator_scopes=frozenset({"system:governance:write"}),
)
engine = create_engine("sqlite+pysqlite:///:memory:")
FormDefinitionRevision.__table__.create(engine)
session = Session(engine)
try:
preflight = _preflight_definition(session, fragment, context)
applied = _apply_definition(session, fragment, context)
session.commit()
replay = _apply_definition(session, fragment, context)
imported = get_form_definition(
session,
type("Principal", (), {"tenant_id": "tenant-reference-test"})(),
form_id="resident-parking-permit-application",
)
finally:
session.close()
engine.dispose()
self.assertFalse(any(item.severity == "blocker" for item in preflight.diagnostics))
self.assertEqual("create", preflight.plan[0].action)
self.assertEqual(1, len(applied.created_refs))
self.assertEqual({}, replay.created_refs)
self.assertIsNotNone(imported)
assert imported is not None
self.assertEqual("tenant-reference-test", imported.reference.tenant_id)
self.assertEqual("draft", imported.publication_state)
self.assertEqual("de", imported.fallback_locale)
def test_service_to_decision_package_materializes_its_tenant_workflow_idempotently(self) -> None:
from govoplan_core.core.configuration_packages import _resolve_fragment_data_references
from govoplan_workflow_engine.backend.configuration_provider import (
apply_workflow_definitions,
preflight_workflow_definitions,
)
from govoplan_workflow_engine.backend.db.models import (
WorkflowDefinition,
WorkflowDefinitionRevision,
)
package = ConfigurationPackageManifest.from_mapping(json.loads(
(PACKAGE_ROOT / "product/service-to-decision/package.json").read_text(
encoding="utf-8"
)
))
fragment = next(
item
for item in package.fragments
if item.module_id == "workflow_engine"
)
context = ConfigurationPreflightContext(
tenant_id="tenant-reference-test",
supplied_data={
"responsible_group_slug": "traffic-permits",
"responsible_group_name": "Traffic permits",
},
)
resolved = _resolve_fragment_data_references(
fragment,
context.supplied_data,
)
engine = create_engine("sqlite+pysqlite:///:memory:")
WorkflowDefinition.__table__.create(engine)
WorkflowDefinitionRevision.__table__.create(engine)
session = Session(engine)
try:
preflight = preflight_workflow_definitions(session, resolved, context)
applied = apply_workflow_definitions(
session,
resolved,
context,
registry=None,
)
replay = apply_workflow_definitions(
session,
resolved,
context,
registry=None,
)
session.commit()
finally:
session.close()
engine.dispose()
self.assertFalse(any(item.severity == "blocker" for item in preflight.diagnostics))
self.assertEqual("create", preflight.plan[0].action)
self.assertIn("resident-parking-permit-review", applied.created_refs)
self.assertEqual({}, replay.created_refs)
self.assertEqual({}, replay.updated_refs)
class _ArtifactProvider:
def __init__(self, module_id: str) -> None:
self.module_id = module_id
def describe(self) -> ConfigurationProviderDescription:
return ConfigurationProviderDescription(module_id=self.module_id)
def preflight(self, fragment, context) -> ConfigurationPreflightResult:
del context
return ConfigurationPreflightResult(plan=(ConfigurationPlanItem(
action="create",
module_id=fragment.module_id,
fragment_type=fragment.fragment_type,
fragment_id=fragment.fragment_id,
),))
def apply(self, fragment, supplied_data, context) -> ConfigurationApplyResult:
del fragment, supplied_data, context
return ConfigurationApplyResult()
def export(self, selection, context) -> ConfigurationExportResult:
del selection, context
return ConfigurationExportResult()
def health(self, import_result, context):
del import_result, context
return ()
def _sample_value(requirement: dict[str, object]) -> object:
data_type = str(requirement.get("data_type") or requirement.get("type") or "string")
if data_type == "boolean":
return False
if data_type in {"integer", "number"}:
return 1
return f"fixture-{requirement['key']}"
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+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.19", 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(
""" """
+4
View File
@@ -115,6 +115,7 @@ PY
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-approvals/tests "$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-approvals/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-identity-trust/tests "$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-identity-trust/tests
"$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-encryption/tests "$PYTHON" -m unittest discover -s /mnt/DATA/git/govoplan-encryption/tests
"$PYTHON" -m pytest -q /mnt/DATA/git/govoplan-wiki/tests
"$PYTHON" -m pytest -q /mnt/DATA/git/govoplan-campaign/tests/test_approval_gate.py "$PYTHON" -m pytest -q /mnt/DATA/git/govoplan-campaign/tests/test_approval_gate.py
"$PYTHON" "$META_ROOT/tools/checks/check-datasource-composition.py" "$PYTHON" "$META_ROOT/tools/checks/check-datasource-composition.py"
"$PYTHON" "$META_ROOT/tools/checks/check-sanctions-screening-composition.py" "$PYTHON" "$META_ROOT/tools/checks/check-sanctions-screening-composition.py"
@@ -160,3 +161,6 @@ cd /mnt/DATA/git/govoplan-campaign/webui
"$NPM" run test:accessibility-contract "$NPM" run test:accessibility-contract
"$NPM" run test:campaign-collaboration "$NPM" run test:campaign-collaboration
"$NPM" run test:campaign-work "$NPM" run test:campaign-work
cd /mnt/DATA/git/govoplan-wiki/webui
"$NPM" run test:interface-pattern
+5 -4
View File
@@ -4,10 +4,12 @@
"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.",
"helpdesk": "Contract-only module: helpdesk requests, queues, and resolutions 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.",
"ops": "Projection-only module: Ops reads bounded platform and provider status; durable recovery evidence remains owned by Core and domain modules.", "ops": "Projection-only module: Ops reads bounded platform and provider status; durable recovery evidence remains owned by Core and domain modules.",
@@ -18,8 +20,7 @@
"rest": "Transport-only module: REST binds explicitly published functions and owns no domain or subject-data store.", "rest": "Transport-only module: REST binds explicitly published functions 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.", "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.",
"tickets": "Contract-only module: ticket, queue, SLA, and adapter persistence are not implemented; 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.", "transparency": "Contract-only module: requests, disclosure reviews, and publications are not persisted; reassess before adding a migration-owned store.",
"wiki": "Contract-only module: wiki spaces, pages, and revisions 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."
} }
@@ -539,6 +539,125 @@
"rationale": "The Connector governance administration page constructs this review-decision URL from the selected run identifier.", "rationale": "The Connector governance administration page constructs this review-decision URL from the selected run identifier.",
"repository": "govoplan-connectors" "repository": "govoplan-connectors"
}, },
{
"category": "ui_reachable",
"method": "GET",
"path": "/connectors/knowledge/profiles",
"rationale": "The External knowledge administration page lists MediaWiki and BlueSpice profiles through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/knowledge/profiles",
"rationale": "The External knowledge administration page creates a mapped, ACL-governed knowledge profile through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "PUT",
"path": "/connectors/knowledge/profiles/{}",
"rationale": "The External knowledge administration page constructs this revision-guarded profile URL from the selected profile identifier.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/knowledge/profiles/{}/discover",
"rationale": "The External knowledge administration page discovers product, version, capabilities, namespaces, and diagnostics through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/knowledge/profiles/{}/migration-dry-runs",
"rationale": "The External knowledge administration page runs a bounded, non-writing native-Wiki migration preview through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "GET",
"path": "/connectors/knowledge/profiles/{}/objects",
"rationale": "The External knowledge administration page lists synchronized, identity-stable knowledge snapshots through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/knowledge/profiles/{}/pages/{}/publish",
"rationale": "The External knowledge administration page constructs this revision-checked external publication URL from the selected profile and page identifiers.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/knowledge/profiles/{}/sync",
"rationale": "The External knowledge administration page runs keyed full backfills and recent-change deltas through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "GET",
"path": "/connectors/knowledge/runs",
"rationale": "The External knowledge administration page lists synchronization, migration-preview, and publication evidence through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "GET",
"path": "/connectors/service-desk/profiles",
"rationale": "The External service desk administration page lists Znuny/OTRS profiles through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/service-desk/profiles",
"rationale": "The External service desk administration page creates route-, queue-, field-, authority-, and ACL-governed profiles through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "PUT",
"path": "/connectors/service-desk/profiles/{}",
"rationale": "The External service desk administration page constructs this revision-guarded profile URL from the selected profile identifier.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/service-desk/profiles/{}/discover",
"rationale": "The External service desk administration page discovers product, version, maturity, capabilities, route health, and diagnostics through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "GET",
"path": "/connectors/service-desk/profiles/{}/objects",
"rationale": "The External service desk administration page lists synchronized identity-stable, ACL-governed external ticket projections through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/service-desk/profiles/{}/sync",
"rationale": "The External service desk administration page runs keyed bounded full and delta synchronization through this endpoint.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "POST",
"path": "/connectors/service-desk/profiles/{}/tickets/{}/update",
"rationale": "The External service desk ticket list opens a separated revision-checked governed update dialog and constructs this URL from stable profile and provider ticket identifiers.",
"repository": "govoplan-connectors"
},
{
"category": "ui_reachable",
"method": "GET",
"path": "/connectors/service-desk/runs",
"rationale": "The External service desk administration page lists synchronization and external-mutation recovery evidence through this endpoint.",
"repository": "govoplan-connectors"
},
{ {
"category": "intentionally_headless", "category": "intentionally_headless",
"method": "GET", "method": "GET",
+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]]: