Compare commits
41
Commits
d08f9f0f2d
...
v0.1.40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3766e26377 | ||
|
|
6c2b36af0f | ||
|
|
3f75ca8e48 | ||
|
|
a886a9b3de | ||
|
|
fe83290d56 | ||
|
|
c50f699399 | ||
|
|
59b45a0829 | ||
|
|
861abcc573 | ||
|
|
5e995fed88 | ||
|
|
41ca242004 | ||
|
|
85caa8d337 | ||
|
|
64640327ae | ||
|
|
cc7c2a91ee | ||
|
|
7c92565d9d | ||
|
|
23bfe5e2f8 | ||
|
|
cf2f7f6890 | ||
|
|
23b601bc0d | ||
|
|
99c52c2153 | ||
|
|
ca68d98806 | ||
|
|
79c4cb067a | ||
|
|
cd498dc1d8 | ||
|
|
e07b3487e3 | ||
|
|
72279de2c0 | ||
|
|
88543ab115 | ||
|
|
81fe0f4680 | ||
|
|
fe784cc562 | ||
|
|
9fe7ad2cb4 | ||
|
|
f1eebd849c | ||
|
|
4eb90079d5 | ||
|
|
628714804b | ||
|
|
ff9fa37a88 | ||
|
|
4c7552f0dd | ||
|
|
a20e02291d | ||
|
|
b6452c6f53 | ||
|
|
75103d49af | ||
|
|
a60b8b0752 | ||
|
|
7f2f896a0f | ||
|
|
60e04a324c | ||
|
|
6a74e53a1c | ||
|
|
6517b6ac27 | ||
|
|
26a66814b4 |
@@ -72,6 +72,23 @@ Each WebUI module should be able to announce:
|
||||
The contract references surfaces. It does not permit Core or a product package
|
||||
to import their implementation.
|
||||
|
||||
The first versioned `product_surfaces` slice is now implemented in Core. It
|
||||
binds a stable product identity and entry path to one or more owner routes,
|
||||
View surfaces, presentations, capabilities, search sources, help contexts and
|
||||
documentation topics. It also carries standard unavailable/degraded
|
||||
explanations and migration aliases. Mail and Postbox contribute the first
|
||||
shared identity, `communication.messages`: `/messages` and the migration alias
|
||||
`/inbox` select the first currently authorized, View-visible owner while the
|
||||
underlying `/mail` and `/postbox` deep links, custody and permissions remain
|
||||
unchanged. Alias resolution emits a bounded client telemetry event before the
|
||||
redirect.
|
||||
|
||||
Core's `ProductAvailabilityState` is the shared presentation primitive for
|
||||
authorization, Policy, configuration, disabled, missing-capability, offline and
|
||||
provider-degraded states. Product language is primary; exact module,
|
||||
capability, provider and correlation provenance is available only in an
|
||||
expandable technical section.
|
||||
|
||||
## Navigation Model
|
||||
|
||||
The default shell should prioritize:
|
||||
@@ -132,9 +149,9 @@ first rail slice.
|
||||
|
||||
### Slice 1: inventory and aliases
|
||||
|
||||
- classify every route, navigation item, widget, setting, search object, and
|
||||
- continue classifying every route, navigation item, widget, setting, search object, and
|
||||
help context by product area and object type;
|
||||
- add product aliases without removing existing deep links;
|
||||
- extend the implemented product-surface aliases without removing existing deep links;
|
||||
- flag raw module IDs in ordinary-user labels and errors.
|
||||
|
||||
### Slice 2: work-first shell
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# DSAR Provider Coverage
|
||||
|
||||
This generated matrix is enforced by `tools/checks/check-dsar-coverage.py`.
|
||||
A migration-owning module must register and document its canonical DSAR provider.
|
||||
Every other active module requires a reviewed explanation of why it owns no
|
||||
persistent subject-data store. Adding a migration invalidates that explanation.
|
||||
|
||||
- Active modules: 72
|
||||
- Registered and documented DSAR providers: 48
|
||||
- Reviewed no-store rationales: 24
|
||||
- Unexplained coverage gaps: 0
|
||||
|
||||
| Module | Repository | Persistence | Coverage | Rationale |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `access` | `govoplan-access` | Migration-owned | Provider | Provider `privacy.dsar.access` is registered and documented. |
|
||||
| `addresses` | `govoplan-addresses` | Migration-owned | Provider | Provider `privacy.dsar.addresses` is registered and documented. |
|
||||
| `admin` | `govoplan-admin` | Migration-owned | Provider | Provider `privacy.dsar.admin` is registered and documented. |
|
||||
| `approvals` | `govoplan-approvals` | Migration-owned | Provider | Provider `privacy.dsar.approvals` is registered and documented. |
|
||||
| `assets` | `govoplan-assets` | No module migration | Reviewed no-store rationale | Contract-only module: asset persistence and lifecycle APIs are not implemented; reassess before adding a migration-owned store. |
|
||||
| `audit` | `govoplan-audit` | Migration-owned | Provider | Provider `privacy.dsar.audit` is registered and documented. |
|
||||
| `booking` | `govoplan-booking` | No module migration | Reviewed no-store rationale | Contract-only module: booking persistence and reservation workflows are not implemented; reassess before adding a migration-owned store. |
|
||||
| `calendar` | `govoplan-calendar` | Migration-owned | Provider | Provider `privacy.dsar.calendar` is registered and documented. |
|
||||
| `campaigns` | `govoplan-campaign` | Migration-owned | Provider | Provider `privacy.dsar.campaigns` is registered and documented. |
|
||||
| `cases` | `govoplan-cases` | Migration-owned | Provider | Provider `privacy.dsar.cases` is registered and documented. |
|
||||
| `certificates` | `govoplan-certificates` | No module migration | Reviewed no-store rationale | Contract-only module: certificate issuance and revocation persistence are not implemented; reassess before adding a migration-owned store. |
|
||||
| `committee` | `govoplan-committee` | Migration-owned | Provider | Provider `privacy.dsar.committee` is registered and documented. |
|
||||
| `connectors` | `govoplan-connectors` | Migration-owned | Provider | Provider `privacy.dsar.connectors` is registered and documented. |
|
||||
| `consultation` | `govoplan-consultation` | No module migration | Reviewed no-store rationale | Contract-only module: consultation submissions and evaluation persistence are not implemented; reassess before adding a migration-owned store. |
|
||||
| `contracts` | `govoplan-contracts` | No module migration | Reviewed no-store rationale | Contract-only module: contract, amendment, and obligation persistence are not implemented; reassess before adding a migration-owned store. |
|
||||
| `dashboard` | `govoplan-dashboard` | Migration-owned | Provider | Provider `privacy.dsar.dashboard` is registered and documented. |
|
||||
| `dataflow` | `govoplan-dataflow` | Migration-owned | Provider | Provider `privacy.dsar.dataflow` 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. |
|
||||
| `dist_lists` | `govoplan-dist-lists` | Migration-owned | Provider | Provider `privacy.dsar.dist_lists` is registered and documented. |
|
||||
| `dms` | `govoplan-dms` | No module migration | Reviewed no-store rationale | Stateless integration-preview module: DMS retains no document, person, credential, or provider-response store; Files and Records remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, or diagnostic. |
|
||||
| `docs` | `govoplan-docs` | Migration-owned | Provider | Provider `privacy.dsar.docs` is registered and documented. |
|
||||
| `encryption` | `govoplan-encryption` | Migration-owned | Provider | Provider `privacy.dsar.encryption` is registered and documented. |
|
||||
| `erp` | `govoplan-erp` | No module migration | Reviewed no-store rationale | Stateless integration-contract module: ERP retains no invoice, payable, plan, booking observation, provider response, or credential store; Procurement, Payments, Ledger, Files, and Audit remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, reconciliation decision, or diagnostic. |
|
||||
| `evaluation` | `govoplan-evaluation` | No module migration | Reviewed no-store rationale | Contract-only module: evaluation runs, responses, and scores are not persisted; reassess before adding a migration-owned store. |
|
||||
| `facilities` | `govoplan-facilities` | No module migration | Reviewed no-store rationale | Contract-only module: facility and maintenance persistence are not implemented; reassess before adding a migration-owned store. |
|
||||
| `files` | `govoplan-files` | Migration-owned | Provider | Provider `privacy.dsar.files` is registered and documented. |
|
||||
| `fit_connect` | `govoplan-fit-connect` | No module migration | Reviewed no-store rationale | Stateless transport-contract module: FIT-Connect retains no submission, attachment, receipt, acknowledgement plan, key, provider response, or diagnostic store; the owning Service, Forms, Cases, Files, and Audit workflows remain responsible for subject data. Reassess before persisting any ingress or event-log evidence. |
|
||||
| `forms` | `govoplan-forms` | Migration-owned | Provider | Provider `privacy.dsar.forms` is registered and documented. |
|
||||
| `forms_runtime` | `govoplan-forms-runtime` | Migration-owned | Provider | Provider `privacy.dsar.forms_runtime` is registered and documented. |
|
||||
| `grants` | `govoplan-grants` | No module migration | Reviewed no-store rationale | Contract-only module: grant applications, awards, and monitoring are not persisted; reassess before adding a migration-owned store. |
|
||||
| `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_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. |
|
||||
| `inspections` | `govoplan-inspections` | No module migration | Reviewed no-store rationale | Contract-only module: inspections, findings, and measures are not persisted; reassess before adding a migration-owned store. |
|
||||
| `learning` | `govoplan-learning` | No module migration | Reviewed no-store rationale | Contract-only module: learning offers, enrollment, and completion are not persisted; reassess before adding a migration-owned store. |
|
||||
| `mail` | `govoplan-mail` | Migration-owned | Provider | Provider `privacy.dsar.mail` is registered and documented. |
|
||||
| `mandates` | `govoplan-mandates` | Migration-owned | Provider | Provider `privacy.dsar.mandates` is registered and documented. |
|
||||
| `notifications` | `govoplan-notifications` | Migration-owned | Provider | Provider `privacy.dsar.notifications` is registered and documented. |
|
||||
| `ops` | `govoplan-ops` | No module migration | Reviewed no-store rationale | Projection-only module: Ops reads bounded platform and provider status; durable recovery evidence remains owned by Core and domain modules. |
|
||||
| `organizations` | `govoplan-organizations` | Migration-owned | Provider | Provider `privacy.dsar.organizations` is registered and documented. |
|
||||
| `parties` | `govoplan-parties` | Migration-owned | Provider | Provider `privacy.dsar.parties` is registered and documented. |
|
||||
| `payments` | `govoplan-payments` | Migration-owned | Provider | Provider `privacy.dsar.payments` is registered and documented. |
|
||||
| `permits` | `govoplan-permits` | No module migration | Reviewed no-store rationale | Contract-only module: permit applications, assessments, and decisions are not persisted; reassess before adding a migration-owned store. |
|
||||
| `policy` | `govoplan-policy` | Migration-owned | Provider | Provider `privacy.dsar.policy` is registered and documented. |
|
||||
| `poll` | `govoplan-poll` | Migration-owned | Provider | Provider `privacy.dsar.poll` is registered and documented. |
|
||||
| `portal` | `govoplan-portal` | No module migration | Reviewed no-store rationale | Projection-only module: Portal stores no applicant records; Services, Forms Runtime, Cases, and Postbox own and export authoritative subject data. |
|
||||
| `postbox` | `govoplan-postbox` | Migration-owned | Provider | Provider `privacy.dsar.postbox` is registered and documented. |
|
||||
| `procurement` | `govoplan-procurement` | No module migration | Reviewed no-store rationale | Contract-only module: procurement procedures, tenders, and awards are not persisted; reassess before adding a migration-owned store. |
|
||||
| `projects` | `govoplan-projects` | Migration-owned | Provider | Provider `privacy.dsar.projects` is registered and documented. |
|
||||
| `quick_access` | `govoplan-quick-access` | Migration-owned | Provider | Provider `privacy.dsar.quick_access` is registered and documented. |
|
||||
| `records` | `govoplan-records` | Migration-owned | Provider | Provider `privacy.dsar.records` is registered and documented. |
|
||||
| `reporting` | `govoplan-reporting` | Migration-owned | Provider | Provider `privacy.dsar.reporting` is registered and documented. |
|
||||
| `resources` | `govoplan-resources` | No module migration | Reviewed no-store rationale | Contract-only module: resource catalog and allocation persistence are not implemented; reassess before adding a migration-owned store. |
|
||||
| `rest` | `govoplan-rest` | No module migration | Reviewed no-store rationale | Transport-only module: REST binds explicitly published functions and owns no domain or subject-data store. |
|
||||
| `risk_compliance` | `govoplan-risk-compliance` | Migration-owned | Provider | Provider `privacy.dsar.risk_compliance` is registered and documented. |
|
||||
| `scheduling` | `govoplan-scheduling` | Migration-owned | Provider | Provider `privacy.dsar.scheduling` is registered and documented. |
|
||||
| `search` | `govoplan-search` | Migration-owned | Provider | Provider `privacy.dsar.search` is registered and documented. |
|
||||
| `services` | `govoplan-services` | Migration-owned | Provider | Provider `privacy.dsar.services` is registered and documented. |
|
||||
| `soap` | `govoplan-soap` | No module migration | Reviewed no-store rationale | Transport-only module: SOAP binds explicitly published operations and owns no domain or subject-data store. |
|
||||
| `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. |
|
||||
| `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` | 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. |
|
||||
| `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. |
|
||||
| `wiki` | `govoplan-wiki` | Migration-owned | Provider | Provider `privacy.dsar.wiki` is registered and documented. |
|
||||
| `workflow` | `govoplan-workflow` | No module migration | Reviewed no-store rationale | Presentation-only module: Workflow edits and projects Workflow Engine state; Workflow Engine owns persistence and DSAR coverage. |
|
||||
| `workflow_engine` | `govoplan-workflow-engine` | Migration-owned | Provider | Provider `privacy.dsar.workflow_engine` is registered and documented. |
|
||||
| `xrechnung` | `govoplan-xrechnung` | No module migration | Reviewed no-store rationale | Stateless validation-contract module: XRechnung persists no invoice, report, diagnostic, or handoff; the invoking Files, Procurement, or Payments workflow remains the subject-data owner. Reassess before adding a validation store. |
|
||||
|
||||
Provider search, export minimization, retention, and erasure behavior remains
|
||||
documented and tested by each owning module. This matrix verifies adoption and
|
||||
ownership coverage; Core continues to test disabled providers, partial failure,
|
||||
retry, authorization evidence, and horizontally coordinated execution.
|
||||
@@ -109,7 +109,9 @@ semantics as authenticated navigation routes.
|
||||
| `/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` |
|
||||
| `/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` |
|
||||
| `/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` |
|
||||
|
||||
## Final Module Closure Evidence
|
||||
@@ -323,8 +325,8 @@ The generated manifest snapshot reports no WebUI package for:
|
||||
`govoplan-learning`, `govoplan-mandates`, `govoplan-parties`,
|
||||
`govoplan-permits`, `govoplan-poll`, `govoplan-procurement`,
|
||||
`govoplan-records`, `govoplan-resources`, `govoplan-rest`,
|
||||
`govoplan-services`, `govoplan-soap`, `govoplan-tickets`,
|
||||
`govoplan-transparency`, `govoplan-wiki`, and `govoplan-workflow-engine`.
|
||||
`govoplan-services`, `govoplan-soap`, `govoplan-transparency`, and
|
||||
`govoplan-workflow-engine`.
|
||||
|
||||
Tenancy does provide composed administration surfaces despite having no direct
|
||||
route. This section is only negative package evidence; connector-only,
|
||||
|
||||
@@ -4,85 +4,89 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan"
|
||||
version = "0.1.18"
|
||||
version = "0.1.40"
|
||||
description = "Developer convenience package for a versioned GovOPlaN composition"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = { text = "AGPL-3.0-or-later" }
|
||||
dependencies = [
|
||||
"govoplan-core[server]==0.1.18",
|
||||
"govoplan-tenancy==0.1.18",
|
||||
"govoplan-organizations==0.1.18",
|
||||
"govoplan-identity==0.1.18",
|
||||
"govoplan-idm==0.1.18",
|
||||
"govoplan-access==0.1.18",
|
||||
"govoplan-admin==0.1.18",
|
||||
"govoplan-policy==0.1.18",
|
||||
"govoplan-audit==0.1.18",
|
||||
"govoplan-dashboard==0.1.18",
|
||||
"govoplan-files==0.1.18",
|
||||
"govoplan-mail==0.1.18",
|
||||
"govoplan-campaign==0.1.18",
|
||||
"govoplan-calendar==0.1.18",
|
||||
"govoplan-docs==0.1.18",
|
||||
"govoplan-ops==0.1.18",
|
||||
"govoplan-core[server]==0.1.40",
|
||||
"govoplan-tenancy==0.1.20",
|
||||
"govoplan-organizations==0.1.20",
|
||||
"govoplan-identity==0.1.20",
|
||||
"govoplan-idm==0.1.24",
|
||||
"govoplan-access==0.1.23",
|
||||
"govoplan-admin==0.1.22",
|
||||
"govoplan-policy==0.1.22",
|
||||
"govoplan-audit==0.1.20",
|
||||
"govoplan-dashboard==0.1.20",
|
||||
"govoplan-files==0.1.23",
|
||||
"govoplan-mail==0.1.25",
|
||||
"govoplan-campaign==0.1.27",
|
||||
"govoplan-calendar==0.1.22",
|
||||
"govoplan-docs==0.1.22",
|
||||
"govoplan-ops==0.1.20",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
full = [
|
||||
"govoplan-addresses==0.1.18",
|
||||
"govoplan-approvals==0.1.18",
|
||||
"govoplan-assets==0.1.18",
|
||||
"govoplan-booking==0.1.18",
|
||||
"govoplan-cases==0.1.18",
|
||||
"govoplan-certificates==0.1.18",
|
||||
"govoplan-committee==0.1.18",
|
||||
"govoplan-connectors==0.1.18",
|
||||
"govoplan-consultation==0.1.18",
|
||||
"govoplan-contracts==0.1.18",
|
||||
"govoplan-dataflow==0.1.18",
|
||||
"govoplan-datasources==0.1.18",
|
||||
"govoplan-decisions==0.1.18",
|
||||
"govoplan-dist-lists==0.1.18",
|
||||
"govoplan-encryption==0.1.18",
|
||||
"govoplan-evaluation==0.1.18",
|
||||
"govoplan-facilities==0.1.18",
|
||||
"govoplan-forms==0.1.18",
|
||||
"govoplan-forms-runtime==0.1.18",
|
||||
"govoplan-grants==0.1.18",
|
||||
"govoplan-helpdesk==0.1.18",
|
||||
"govoplan-identity-trust==0.1.18",
|
||||
"govoplan-inspections==0.1.18",
|
||||
"govoplan-learning==0.1.18",
|
||||
"govoplan-mandates==0.1.18",
|
||||
"govoplan-notifications==0.1.18",
|
||||
"govoplan-parties==0.1.18",
|
||||
"govoplan-payments==0.1.20",
|
||||
"govoplan-permits==0.1.18",
|
||||
"govoplan-poll==0.1.18",
|
||||
"govoplan-portal==0.1.18",
|
||||
"govoplan-postbox==0.1.18",
|
||||
"govoplan-procurement==0.1.18",
|
||||
"govoplan-projects==0.1.18",
|
||||
"govoplan-quick-access==0.1.18",
|
||||
"govoplan-records==0.1.19",
|
||||
"govoplan-reporting==0.1.18",
|
||||
"govoplan-resources==0.1.18",
|
||||
"govoplan-rest==0.1.18",
|
||||
"govoplan-risk-compliance==0.1.18",
|
||||
"govoplan-scheduling==0.1.18",
|
||||
"govoplan-search==0.1.18",
|
||||
"govoplan-services==0.1.18",
|
||||
"govoplan-soap==0.1.18",
|
||||
"govoplan-tasks==0.1.19",
|
||||
"govoplan-templates==0.1.18",
|
||||
"govoplan-tickets==0.1.18",
|
||||
"govoplan-transparency==0.1.18",
|
||||
"govoplan-views==0.1.18",
|
||||
"govoplan-voting==0.1.18",
|
||||
"govoplan-wiki==0.1.18",
|
||||
"govoplan-workflow==0.1.18",
|
||||
"govoplan-workflow-engine==0.1.18",
|
||||
"govoplan-addresses==0.1.21",
|
||||
"govoplan-approvals==0.1.20",
|
||||
"govoplan-assets==0.1.20",
|
||||
"govoplan-booking==0.1.20",
|
||||
"govoplan-cases==0.1.22",
|
||||
"govoplan-certificates==0.1.20",
|
||||
"govoplan-committee==0.1.20",
|
||||
"govoplan-connectors==0.1.25",
|
||||
"govoplan-consultation==0.1.20",
|
||||
"govoplan-contracts==0.1.20",
|
||||
"govoplan-dataflow==0.1.23",
|
||||
"govoplan-datasources==0.1.24",
|
||||
"govoplan-decisions==0.1.19",
|
||||
"govoplan-dist-lists==0.1.20",
|
||||
"govoplan-dms==0.1.20",
|
||||
"govoplan-encryption==0.1.19",
|
||||
"govoplan-erp==0.1.20",
|
||||
"govoplan-evaluation==0.1.20",
|
||||
"govoplan-facilities==0.1.20",
|
||||
"govoplan-fit-connect==0.1.20",
|
||||
"govoplan-forms==0.1.22",
|
||||
"govoplan-forms-runtime==0.1.19",
|
||||
"govoplan-grants==0.1.20",
|
||||
"govoplan-helpdesk==0.1.21",
|
||||
"govoplan-identity-trust==0.1.20",
|
||||
"govoplan-inspections==0.1.20",
|
||||
"govoplan-learning==0.1.20",
|
||||
"govoplan-mandates==0.1.19",
|
||||
"govoplan-notifications==0.1.19",
|
||||
"govoplan-parties==0.1.19",
|
||||
"govoplan-payments==0.1.21",
|
||||
"govoplan-permits==0.1.20",
|
||||
"govoplan-poll==0.1.20",
|
||||
"govoplan-portal==0.1.21",
|
||||
"govoplan-postbox==0.1.22",
|
||||
"govoplan-procurement==0.1.20",
|
||||
"govoplan-projects==0.1.19",
|
||||
"govoplan-quick-access==0.1.20",
|
||||
"govoplan-records==0.1.22",
|
||||
"govoplan-reporting==0.1.20",
|
||||
"govoplan-resources==0.1.20",
|
||||
"govoplan-rest==0.1.19",
|
||||
"govoplan-risk-compliance==0.1.20",
|
||||
"govoplan-scheduling==0.1.21",
|
||||
"govoplan-search==0.1.19",
|
||||
"govoplan-services==0.1.19",
|
||||
"govoplan-soap==0.1.19",
|
||||
"govoplan-tasks==0.1.21",
|
||||
"govoplan-templates==0.1.21",
|
||||
"govoplan-tickets==0.1.22",
|
||||
"govoplan-transparency==0.1.20",
|
||||
"govoplan-views==0.1.21",
|
||||
"govoplan-voting==0.1.20",
|
||||
"govoplan-wiki==0.1.22",
|
||||
"govoplan-workflow==0.1.22",
|
||||
"govoplan-workflow-engine==0.1.21",
|
||||
"govoplan-xrechnung==0.1.21",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -17,6 +17,26 @@ human review handoff, formal outcome, Postbox delivery channel, and Records
|
||||
filing/retention target. Generic permit wording is no longer acceptance
|
||||
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
|
||||
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
|
||||
are still required before this product package may claim `reference_ready`
|
||||
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",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"required_modules": [
|
||||
{"module_id": "access"},
|
||||
{"module_id": "audit"},
|
||||
{"module_id": "cases"},
|
||||
{"module_id": "decisions"},
|
||||
{"module_id": "forms"},
|
||||
{"module_id": "forms_runtime"},
|
||||
{"module_id": "mandates"},
|
||||
{"module_id": "parties"},
|
||||
{"module_id": "payments"},
|
||||
{"module_id": "policy"},
|
||||
{"module_id": "portal"},
|
||||
{"module_id": "services"}
|
||||
{"module_id": "services"},
|
||||
{"module_id": "tasks"},
|
||||
{"module_id": "workflow_engine"}
|
||||
],
|
||||
"required_capabilities": [
|
||||
"access.configuration",
|
||||
"cases.party_context",
|
||||
"cases.service_intake",
|
||||
"decisions.registry",
|
||||
"forms.configuration",
|
||||
"forms.definitions",
|
||||
"mandates.resolver",
|
||||
"parties.resolver",
|
||||
"payments.requests",
|
||||
"portal.service_directory",
|
||||
"services.availability",
|
||||
"services.definitions"
|
||||
"services.definitions",
|
||||
"workflow.configuration"
|
||||
],
|
||||
"optional_modules": [
|
||||
{"module_id": "approvals"},
|
||||
{"module_id": "committee"},
|
||||
{"module_id": "files"},
|
||||
{"module_id": "forms"},
|
||||
{"module_id": "forms_runtime"},
|
||||
{"module_id": "postbox"},
|
||||
{"module_id": "records"},
|
||||
{"module_id": "search"},
|
||||
{"module_id": "tasks"},
|
||||
{"module_id": "workflow_engine"}
|
||||
{"module_id": "search"}
|
||||
],
|
||||
"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": [
|
||||
{
|
||||
|
||||
+15
-15
@@ -1,18 +1,18 @@
|
||||
# Whole-product release install from immutable, independently versioned module tags.
|
||||
# Only add a module after its referenced tag has been published.
|
||||
../govoplan-core[server]
|
||||
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.18
|
||||
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.18
|
||||
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.18
|
||||
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.18
|
||||
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.18
|
||||
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.18
|
||||
govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.18
|
||||
govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.18
|
||||
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.18
|
||||
govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.18
|
||||
govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.18
|
||||
govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.18
|
||||
govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.18
|
||||
govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.18
|
||||
govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.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.20
|
||||
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.20
|
||||
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.24
|
||||
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.23
|
||||
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.22
|
||||
govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.22
|
||||
govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.20
|
||||
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.20
|
||||
govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.23
|
||||
govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.25
|
||||
govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.27
|
||||
govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.22
|
||||
govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.22
|
||||
govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.20
|
||||
|
||||
@@ -5,9 +5,17 @@ import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.core.configuration_packages import (
|
||||
ConfigurationApplyResult,
|
||||
ConfigurationExportResult,
|
||||
ConfigurationPackageManifest,
|
||||
ConfigurationPlanItem,
|
||||
ConfigurationPreflightContext,
|
||||
ConfigurationPreflightResult,
|
||||
ConfigurationProviderDescription,
|
||||
configuration_package_claim_issues,
|
||||
dry_run_configuration_package,
|
||||
)
|
||||
@@ -54,10 +62,19 @@ class ConfigurationPackageArtifactTests(unittest.TestCase):
|
||||
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(
|
||||
manifest,
|
||||
(),
|
||||
providers,
|
||||
ConfigurationPreflightContext(
|
||||
supplied_data=supplied_data,
|
||||
installed_modules={
|
||||
item.module_id: item.version or "workspace"
|
||||
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.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__":
|
||||
unittest.main()
|
||||
|
||||
@@ -57,18 +57,23 @@ class PackageRegistryReleaseTests(unittest.TestCase):
|
||||
self.assertEqual(ARTIFACTS._canonical_sha256(unsigned), digest)
|
||||
|
||||
def test_full_profile_is_derived_from_the_developer_meta_package(self) -> None:
|
||||
core_version = tomllib.loads(
|
||||
(ROOT.parent / "govoplan-core/pyproject.toml").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
)["project"]["version"]
|
||||
selected = PACKAGE_SET.parse_meta_package(
|
||||
ROOT / "packages/govoplan-meta/pyproject.toml",
|
||||
core_version="0.1.18",
|
||||
core_version=core_version,
|
||||
)
|
||||
|
||||
by_name = {item["name"]: item for item in selected}
|
||||
self.assertIn("govoplan-core", by_name)
|
||||
self.assertIn("govoplan-records", by_name)
|
||||
self.assertEqual("0.1.19", by_name["govoplan-tasks"]["version"])
|
||||
self.assertEqual("0.1.21", by_name["govoplan-tasks"]["version"])
|
||||
|
||||
payload = PACKAGE_SET.generate_package_set(
|
||||
core_version="0.1.18",
|
||||
core_version=core_version,
|
||||
requirements=ROOT / "requirements-release.txt",
|
||||
workspace=ROOT.parent,
|
||||
profile="full",
|
||||
|
||||
@@ -22,11 +22,16 @@ class PackageSetDispatchTests(unittest.TestCase):
|
||||
def test_meta_package_resolves_to_exact_tagged_repository_targets(self) -> None:
|
||||
targets = MODULE.package_targets()
|
||||
|
||||
self.assertEqual(66, len(targets))
|
||||
self.assertEqual(66, len({target.distribution for target in targets}))
|
||||
self.assertEqual(73, len(targets))
|
||||
self.assertEqual(73, len({target.distribution for target in targets}))
|
||||
by_name = {target.distribution: target for target in targets}
|
||||
self.assertEqual("v0.1.14", by_name["govoplan-core"].tag)
|
||||
self.assertEqual("v0.1.8", by_name["govoplan-access"].tag)
|
||||
self.assertEqual("v0.1.38", by_name["govoplan-core"].tag)
|
||||
self.assertEqual("v0.1.22", by_name["govoplan-access"].tag)
|
||||
self.assertEqual("v0.1.20", by_name["govoplan-dms"].tag)
|
||||
self.assertEqual("v0.1.20", by_name["govoplan-erp"].tag)
|
||||
self.assertEqual("v0.1.20", by_name["govoplan-fit-connect"].tag)
|
||||
self.assertEqual("v0.1.23", by_name["govoplan-idm"].tag)
|
||||
self.assertEqual("v0.1.21", by_name["govoplan-xrechnung"].tag)
|
||||
self.assertTrue(by_name["govoplan-core"].tag_exists)
|
||||
self.assertTrue(by_name["govoplan-access"].has_webui)
|
||||
self.assertEqual(
|
||||
|
||||
@@ -169,6 +169,69 @@ class PlatformInterfaceInventoryTests(unittest.TestCase):
|
||||
),
|
||||
)
|
||||
|
||||
def test_high_risk_help_baseline_is_validated(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "help-baseline.json"
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": 1,
|
||||
"maximum_missing_exact_help": 3,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
3,
|
||||
inventory._load_high_risk_help_baseline(path)[
|
||||
"maximum_missing_exact_help"
|
||||
],
|
||||
)
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": 1,
|
||||
"maximum_missing_exact_help": -1,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "non-negative integer"):
|
||||
inventory._load_high_risk_help_baseline(path)
|
||||
|
||||
def test_declaration_strict_mode_rejects_high_risk_help_regression(
|
||||
self,
|
||||
) -> None:
|
||||
result = {
|
||||
"translation_health": {"missing_catalog_entries": []},
|
||||
"api": {
|
||||
"unclassified_endpoints": [],
|
||||
"stale_endpoint_declarations": [],
|
||||
},
|
||||
"declaration_health": {},
|
||||
"help_health": {
|
||||
"invalid_risk_annotations": [],
|
||||
"unresolved_exact_high_risk_help": [],
|
||||
"high_risk_help_without_german": [],
|
||||
"missing_exact_high_risk_help": [{"id": "example.delete"}],
|
||||
"baseline_maximum_missing": 0,
|
||||
"baseline_regression": True,
|
||||
},
|
||||
}
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
"1 high-risk controls lack exact F1 help; baseline permits at most 0"
|
||||
],
|
||||
inventory._strict_failures(
|
||||
result,
|
||||
check_translations=False,
|
||||
check_endpoints=False,
|
||||
check_declarations=True,
|
||||
),
|
||||
)
|
||||
|
||||
def test_fastapi_route_scanner_includes_router_prefix(self) -> None:
|
||||
tree = ast.parse(
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Require DSAR coverage or a reviewed no-store rationale for every module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
META_ROOT = Path(__file__).resolve().parents[2]
|
||||
EXEMPTIONS_PATH = Path(__file__).with_name("dsar-coverage-exemptions.json")
|
||||
REPORT_PATH = (
|
||||
META_ROOT
|
||||
/ "docs"
|
||||
/ "evidence"
|
||||
/ "snapshots"
|
||||
/ "DSAR_PROVIDER_COVERAGE.generated.md"
|
||||
)
|
||||
MODULE_NAME_PATTERN = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*")
|
||||
REQUIRED_DOCUMENTATION_TYPES = frozenset({"admin"})
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CoverageRow:
|
||||
module_id: str
|
||||
repository: str
|
||||
migration_owned: bool
|
||||
capability: str | None
|
||||
rationale: str
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--workspace-root",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Directory containing GovOPlaN repositories.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--render",
|
||||
action="store_true",
|
||||
help="Print the current matrix instead of comparing the checked-in report.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
catalog = json.loads((META_ROOT / "repositories.json").read_text(encoding="utf-8"))
|
||||
workspace_root = (args.workspace_root or Path(catalog["default_parent"])).resolve()
|
||||
exemptions = _exemptions()
|
||||
manifests, load_errors = _load_manifests(
|
||||
workspace_root=workspace_root,
|
||||
repositories=tuple(catalog["repositories"]),
|
||||
)
|
||||
|
||||
errors = list(load_errors)
|
||||
rows: list[CoverageRow] = []
|
||||
manifest_ids = {manifest.id for _, manifest in manifests}
|
||||
stale_exemptions = sorted(set(exemptions) - manifest_ids)
|
||||
if stale_exemptions:
|
||||
errors.append(
|
||||
"DSAR coverage exemptions reference unknown modules: "
|
||||
+ ", ".join(stale_exemptions)
|
||||
)
|
||||
|
||||
for repository, manifest in manifests:
|
||||
expected = f"privacy.dsar.{manifest.id}"
|
||||
provided = {
|
||||
item.name
|
||||
for item in manifest.provides_interfaces
|
||||
if item.name.startswith("privacy.dsar.")
|
||||
}
|
||||
factories = {
|
||||
name
|
||||
for name in manifest.capability_factories
|
||||
if name.startswith("privacy.dsar.")
|
||||
}
|
||||
migration_owned = manifest.migration_spec is not None
|
||||
rationale = exemptions.get(manifest.id)
|
||||
|
||||
if provided != factories:
|
||||
errors.append(
|
||||
f"{repository}: DSAR interface/factory mismatch: "
|
||||
f"interfaces={sorted(provided)!r}, factories={sorted(factories)!r}"
|
||||
)
|
||||
if provided and provided != {expected}:
|
||||
errors.append(
|
||||
f"{repository}: expected only {expected!r}, found {sorted(provided)!r}"
|
||||
)
|
||||
|
||||
capability = (
|
||||
expected if expected in provided and expected in factories else None
|
||||
)
|
||||
if migration_owned and capability is None:
|
||||
errors.append(
|
||||
f"{repository}: migration-owning module {manifest.id!r} must provide "
|
||||
f"and register {expected!r}"
|
||||
)
|
||||
if migration_owned and rationale is not None:
|
||||
errors.append(
|
||||
f"{repository}: migration-owning module {manifest.id!r} cannot use a "
|
||||
"no-store DSAR exemption"
|
||||
)
|
||||
if not migration_owned and capability is None and rationale is None:
|
||||
errors.append(
|
||||
f"{repository}: module {manifest.id!r} needs a DSAR provider or an "
|
||||
"explicit reviewed no-store rationale"
|
||||
)
|
||||
if capability is not None and rationale is not None:
|
||||
errors.append(
|
||||
f"{repository}: module {manifest.id!r} has both DSAR coverage and a "
|
||||
"stale exemption"
|
||||
)
|
||||
if capability is not None:
|
||||
if capability not in manifest.capability_documentation:
|
||||
errors.append(
|
||||
f"{repository}: {capability!r} lacks capability documentation"
|
||||
)
|
||||
matching_topics = tuple(
|
||||
topic
|
||||
for topic in manifest.documentation
|
||||
if "data-subject-request" in topic.id
|
||||
)
|
||||
if not matching_topics or not any(
|
||||
REQUIRED_DOCUMENTATION_TYPES.issubset(topic.documentation_types)
|
||||
for topic in matching_topics
|
||||
):
|
||||
errors.append(
|
||||
f"{repository}: DSAR coverage needs a static administrator "
|
||||
"data-subject-requests DocumentationTopic"
|
||||
)
|
||||
|
||||
rows.append(
|
||||
CoverageRow(
|
||||
module_id=manifest.id,
|
||||
repository=repository,
|
||||
migration_owned=migration_owned,
|
||||
capability=capability,
|
||||
rationale=(
|
||||
f"Provider `{capability}` is registered and documented."
|
||||
if capability
|
||||
else rationale or "MISSING"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
report = _report(rows)
|
||||
if args.render:
|
||||
print(report, end="")
|
||||
elif not REPORT_PATH.is_file():
|
||||
errors.append(f"DSAR coverage report is missing: {REPORT_PATH}")
|
||||
elif REPORT_PATH.read_text(encoding="utf-8") != report:
|
||||
errors.append(
|
||||
"DSAR coverage report is stale; review changes and replace it with "
|
||||
"the output of tools/checks/check-dsar-coverage.py --render"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print("\n".join(errors), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
provider_count = sum(row.capability is not None for row in rows)
|
||||
print(
|
||||
"DSAR coverage check passed: "
|
||||
f"{provider_count} providers, {len(rows) - provider_count} reviewed "
|
||||
f"no-store rationales, {len(rows)} active modules."
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _exemptions() -> dict[str, str]:
|
||||
values = json.loads(EXEMPTIONS_PATH.read_text(encoding="utf-8"))
|
||||
if not isinstance(values, dict) or any(
|
||||
not isinstance(key, str) or not isinstance(value, str) or not value.strip()
|
||||
for key, value in values.items()
|
||||
):
|
||||
raise ValueError("DSAR coverage exemptions must be non-empty string mappings.")
|
||||
return {key: value.strip() for key, value in values.items()}
|
||||
|
||||
|
||||
def _load_manifests(*, workspace_root: Path, repositories: tuple[dict, ...]):
|
||||
sources: list[Path] = []
|
||||
candidates: list[tuple[str, Path, Path]] = []
|
||||
for repository in repositories:
|
||||
source = workspace_root / repository["path"] / "src"
|
||||
if not source.is_dir():
|
||||
continue
|
||||
sources.append(source)
|
||||
candidates.extend(
|
||||
(repository["name"], source, path)
|
||||
for path in sorted(source.glob("*/backend/manifest.py"))
|
||||
)
|
||||
core_source = workspace_root / "govoplan-core" / "src"
|
||||
sys.path[:0] = [
|
||||
str(core_source),
|
||||
*(str(source) for source in sources if source != core_source),
|
||||
]
|
||||
|
||||
manifests = []
|
||||
errors = []
|
||||
for repository, source, path in candidates:
|
||||
module_name = ".".join(path.relative_to(source).with_suffix("").parts)
|
||||
if MODULE_NAME_PATTERN.fullmatch(module_name) is None:
|
||||
errors.append(f"{repository}: unsafe manifest module name {module_name!r}")
|
||||
continue
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
manifests.append((repository, module.get_manifest()))
|
||||
except Exception as exc: # pragma: no cover - emitted as check evidence
|
||||
errors.append(f"{repository}: could not load {module_name}: {exc}")
|
||||
return manifests, errors
|
||||
|
||||
|
||||
def _report(rows: list[CoverageRow]) -> str:
|
||||
ordered = sorted(rows, key=lambda row: row.module_id)
|
||||
providers = sum(row.capability is not None for row in ordered)
|
||||
lines = [
|
||||
"# DSAR Provider Coverage",
|
||||
"",
|
||||
"This generated matrix is enforced by `tools/checks/check-dsar-coverage.py`.",
|
||||
"A migration-owning module must register and document its canonical DSAR provider.",
|
||||
"Every other active module requires a reviewed explanation of why it owns no",
|
||||
"persistent subject-data store. Adding a migration invalidates that explanation.",
|
||||
"",
|
||||
f"- Active modules: {len(ordered)}",
|
||||
f"- Registered and documented DSAR providers: {providers}",
|
||||
f"- Reviewed no-store rationales: {len(ordered) - providers}",
|
||||
"- Unexplained coverage gaps: 0",
|
||||
"",
|
||||
"| Module | Repository | Persistence | Coverage | Rationale |",
|
||||
"| --- | --- | --- | --- | --- |",
|
||||
]
|
||||
for row in ordered:
|
||||
lines.append(
|
||||
"| "
|
||||
+ " | ".join(
|
||||
(
|
||||
f"`{row.module_id}`",
|
||||
f"`{row.repository}`",
|
||||
"Migration-owned" if row.migration_owned else "No module migration",
|
||||
"Provider" if row.capability else "Reviewed no-store rationale",
|
||||
row.rationale.replace("|", "\\|"),
|
||||
)
|
||||
)
|
||||
+ " |"
|
||||
)
|
||||
lines.extend(
|
||||
(
|
||||
"",
|
||||
"Provider search, export minimization, retention, and erasure behavior remains",
|
||||
"documented and tested by each owning module. This matrix verifies adoption and",
|
||||
"ownership coverage; Core continues to test disabled providers, partial failure,",
|
||||
"retry, authorization evidence, and horizontally coordinated execution.",
|
||||
"",
|
||||
)
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -38,6 +38,7 @@ cd "$ROOT"
|
||||
GOVOPLAN_CORE_ROOT="$ROOT" PYTHON="$PYTHON" CHECK_TESTCLIENT_DEPRECATIONS=1 bash "$META_ROOT/tools/checks/check-dependency-hygiene.sh"
|
||||
"$PYTHON" "$META_ROOT/tools/checks/check-contracts.py" --no-impact
|
||||
PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$META_ROOT/tools/checks/check-manifest-shapes.py" --require-architecture
|
||||
PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$META_ROOT/tools/checks/check-dsar-coverage.py"
|
||||
|
||||
cd "$META_ROOT"
|
||||
"$PYTHON" tools/inventory/platform-interface-inventory.py --strict-declarations --strict-endpoints
|
||||
@@ -114,6 +115,7 @@ PY
|
||||
"$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-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" "$META_ROOT/tools/checks/check-datasource-composition.py"
|
||||
"$PYTHON" "$META_ROOT/tools/checks/check-sanctions-screening-composition.py"
|
||||
@@ -157,3 +159,8 @@ cd /mnt/DATA/git/govoplan-campaign/webui
|
||||
"$NPM" run test:policy-ui
|
||||
"$NPM" run test:template-preview
|
||||
"$NPM" run test:accessibility-contract
|
||||
"$NPM" run test:campaign-collaboration
|
||||
"$NPM" run test:campaign-work
|
||||
|
||||
cd /mnt/DATA/git/govoplan-wiki/webui
|
||||
"$NPM" run test:interface-pattern
|
||||
|
||||
@@ -262,6 +262,8 @@ cd "$WORK_ROOT/govoplan-campaign/webui"
|
||||
"$NPM" run test:policy-ui
|
||||
"$NPM" run test:template-preview
|
||||
"$NPM" run test:import-utils
|
||||
"$NPM" run test:campaign-collaboration
|
||||
"$NPM" run test:campaign-work
|
||||
|
||||
echo
|
||||
echo "Release integration check passed."
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"assets": "Contract-only module: asset persistence and lifecycle APIs are not implemented; reassess before adding a migration-owned store.",
|
||||
"booking": "Contract-only module: booking persistence and reservation workflows 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.",
|
||||
"contracts": "Contract-only module: contract, amendment, and obligation persistence are not implemented; reassess before adding a migration-owned store.",
|
||||
"dms": "Stateless integration-preview module: DMS retains no document, person, credential, or provider-response store; Files and Records remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, or diagnostic.",
|
||||
"erp": "Stateless integration-contract module: ERP retains no invoice, payable, plan, booking observation, provider response, or credential store; Procurement, Payments, Ledger, Files, and Audit remain the subject-data owners. Reassess before persisting a target binding, plan, receipt, reconciliation decision, or diagnostic.",
|
||||
"evaluation": "Contract-only module: evaluation runs, responses, and scores are not persisted; reassess before adding a migration-owned store.",
|
||||
"facilities": "Contract-only module: facility and maintenance persistence are not implemented; reassess before adding a migration-owned store.",
|
||||
"fit_connect": "Stateless transport-contract module: FIT-Connect retains no submission, attachment, receipt, acknowledgement plan, key, provider response, or diagnostic store; the owning Service, Forms, Cases, Files, and Audit workflows remain responsible for subject data. Reassess before persisting any ingress or event-log evidence.",
|
||||
"grants": "Contract-only module: grant applications, awards, and monitoring are not persisted; reassess before adding a migration-owned store.",
|
||||
"inspections": "Contract-only module: inspections, findings, and measures are not persisted; reassess before adding a migration-owned store.",
|
||||
"learning": "Contract-only module: learning offers, enrollment, and completion are not persisted; reassess before adding a migration-owned store.",
|
||||
"ops": "Projection-only module: Ops reads bounded platform and provider status; durable recovery evidence remains owned by Core and domain modules.",
|
||||
"permits": "Contract-only module: permit applications, assessments, and decisions are not persisted; reassess before adding a migration-owned store.",
|
||||
"portal": "Projection-only module: Portal stores no applicant records; Services, Forms Runtime, Cases, and Postbox own and export authoritative subject data.",
|
||||
"procurement": "Contract-only module: procurement procedures, tenders, and awards are not persisted; reassess before adding a migration-owned store.",
|
||||
"resources": "Contract-only module: resource catalog and allocation persistence are not implemented; reassess before adding a migration-owned 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.",
|
||||
"tenancy": "Orchestration module: tenant lifecycle and settings use Core-owned storage; Access covers account and membership subject data.",
|
||||
"transparency": "Contract-only module: requests, disclosure reviews, and publications are not persisted; reassess before adding a migration-owned store.",
|
||||
"workflow": "Presentation-only module: Workflow edits and projects Workflow Engine state; Workflow Engine owns persistence and DSAR coverage.",
|
||||
"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.",
|
||||
"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",
|
||||
"method": "GET",
|
||||
@@ -574,6 +693,13 @@
|
||||
"rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.",
|
||||
"repository": "govoplan-connectors"
|
||||
},
|
||||
{
|
||||
"category": "intentionally_headless",
|
||||
"method": "POST",
|
||||
"path": "/connectors/tabular-sources/files",
|
||||
"rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.",
|
||||
"repository": "govoplan-connectors"
|
||||
},
|
||||
{
|
||||
"category": "intentionally_headless",
|
||||
"method": "POST",
|
||||
@@ -581,6 +707,13 @@
|
||||
"rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.",
|
||||
"repository": "govoplan-connectors"
|
||||
},
|
||||
{
|
||||
"category": "intentionally_headless",
|
||||
"method": "POST",
|
||||
"path": "/connectors/tabular-sources/sql",
|
||||
"rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.",
|
||||
"repository": "govoplan-connectors"
|
||||
},
|
||||
{
|
||||
"category": "intentionally_headless",
|
||||
"method": "DELETE",
|
||||
@@ -595,6 +728,13 @@
|
||||
"rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.",
|
||||
"repository": "govoplan-connectors"
|
||||
},
|
||||
{
|
||||
"category": "intentionally_headless",
|
||||
"method": "POST",
|
||||
"path": "/connectors/tabular-sources/{}/refresh",
|
||||
"rationale": "This capability-first module intentionally exposes a headless API for other modules and integrations.",
|
||||
"repository": "govoplan-connectors"
|
||||
},
|
||||
{
|
||||
"category": "public_integration",
|
||||
"method": "GET",
|
||||
@@ -763,6 +903,90 @@
|
||||
"rationale": "The module WebUI constructs this endpoint through a mounted router prefix, generic action, or provider path.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/semantic/entries",
|
||||
"rationale": "The Docs semantic authoring UI calls this mounted sub-router through the /docs prefix, which static endpoint matching cannot compose.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/semantic/entries",
|
||||
"rationale": "The Docs semantic authoring UI calls this mounted sub-router through the /docs prefix, which static endpoint matching cannot compose.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/semantic/entries/{}",
|
||||
"rationale": "The Docs semantic authoring UI constructs the entry identifier dynamically on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "PUT",
|
||||
"path": "/semantic/entries/{}",
|
||||
"rationale": "The Docs semantic authoring UI constructs the entry identifier dynamically on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/semantic/entries/{}/history",
|
||||
"rationale": "The Docs semantic authoring UI constructs immutable history paths dynamically on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/semantic/entries/{}/publish",
|
||||
"rationale": "The Docs semantic authoring UI constructs lifecycle action paths dynamically on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/semantic/entries/{}/retire",
|
||||
"rationale": "The Docs semantic authoring UI constructs lifecycle action paths dynamically on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/semantic/entries/{}/supersede",
|
||||
"rationale": "The Docs semantic authoring UI constructs lifecycle action paths dynamically on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/semantic/export",
|
||||
"rationale": "The Docs semantic authoring UI calls tenant export on the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/semantic/policy",
|
||||
"rationale": "The Docs semantic authoring UI reads publication policy through the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "PUT",
|
||||
"path": "/semantic/policy",
|
||||
"rationale": "The Docs semantic authoring UI saves publication policy through the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/semantic/subjects",
|
||||
"rationale": "The Docs semantic authoring UI discovers authorized module subjects through the mounted semantic sub-router.",
|
||||
"repository": "govoplan-docs"
|
||||
},
|
||||
{
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
@@ -1904,68 +2128,60 @@
|
||||
"repository": "govoplan-campaign"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/relationships",
|
||||
"rationale": "IDM documents typed relationships but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "The IDM relationship administration grid lists effective-dated identity relationships and their lifecycle state.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/relationships",
|
||||
"rationale": "IDM documents typed relationships but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "Authorized IDM administrators create relationships through the searchable relationship editor.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "PATCH",
|
||||
"path": "/relationships/{}",
|
||||
"rationale": "IDM documents typed relationships but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "Authorized IDM administrators update a loaded relationship with its optimistic revision.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/relationships/{}/revoke",
|
||||
"rationale": "IDM documents typed relationships but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "The IDM relationship grid exposes a separated destructive action with mandatory reason and confirmation.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/typed-groups",
|
||||
"rationale": "IDM documents typed groups but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "The IDM typed-group administration grid lists active and optionally inactive groups.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "POST",
|
||||
"path": "/typed-groups",
|
||||
"rationale": "IDM documents typed groups but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "Authorized IDM administrators create typed groups through the group editor.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "PATCH",
|
||||
"path": "/typed-groups/{}",
|
||||
"rationale": "IDM documents typed groups but its current administration page exposes function assignments only.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "Authorized IDM administrators edit group metadata, lifecycle state, and provenance with optimistic revision checks.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "missing_ui",
|
||||
"category": "ui_reachable",
|
||||
"method": "GET",
|
||||
"path": "/typed-groups/{}/memberships",
|
||||
"rationale": "IDM lacks the typed-group membership explanation surface for this existing API.",
|
||||
"repository": "govoplan-idm",
|
||||
"tracking_issue": "https://git.add-ideas.de/GovOPlaN/govoplan-idm/issues/11"
|
||||
"rationale": "The IDM membership inspector resolves a group at a selected time and shows included and excluded decisions.",
|
||||
"repository": "govoplan-idm"
|
||||
},
|
||||
{
|
||||
"category": "intentionally_headless",
|
||||
|
||||
@@ -56,11 +56,35 @@ const labelAttributes = new Set([
|
||||
"title"
|
||||
]);
|
||||
const helpAttributes = new Set([
|
||||
"data-help-context-id",
|
||||
"description",
|
||||
"help",
|
||||
"helpContextId",
|
||||
"helperText",
|
||||
"helpText"
|
||||
]);
|
||||
const exactHelpAttributes = new Set([
|
||||
"data-help-context-id",
|
||||
"helpContextId"
|
||||
]);
|
||||
const helpRiskAttributes = new Set([
|
||||
"data-help-risk",
|
||||
"helpRisk"
|
||||
]);
|
||||
const reviewedHelpRiskAttributes = new Set([
|
||||
"data-help-risk-reviewed",
|
||||
"helpRiskReviewed"
|
||||
]);
|
||||
const supportedHelpRisks = new Set([
|
||||
"authority",
|
||||
"credential",
|
||||
"disclosure",
|
||||
"encryption",
|
||||
"external-effect",
|
||||
"irreversible",
|
||||
"policy",
|
||||
"retention"
|
||||
]);
|
||||
const actionComponentPattern = /(?:Action|Button|Link)$/;
|
||||
const contributionTypes = new Map([
|
||||
["AdminSectionsUiCapability", "admin_section"],
|
||||
@@ -198,20 +222,43 @@ function inspectSource(repository, sourceRoot, sourcePath) {
|
||||
const parentAttributes = parentFormField
|
||||
? jsxAttributes(parentFormField)
|
||||
: new Map();
|
||||
const scopedAncestorAttributes = nearestScopedHelpAttributes(node);
|
||||
const label =
|
||||
attributes.get("label") ??
|
||||
attributes.get("aria-label") ??
|
||||
parentAttributes.get("label") ??
|
||||
null;
|
||||
const help = firstAttribute(attributes, helpAttributes) ??
|
||||
firstAttribute(parentAttributes, helpAttributes);
|
||||
firstAttribute(parentAttributes, helpAttributes) ??
|
||||
firstAttribute(scopedAncestorAttributes, helpAttributes);
|
||||
const hasHelp = hasAnyAttribute(attributes, helpAttributes) ||
|
||||
hasAnyAttribute(parentAttributes, helpAttributes);
|
||||
hasAnyAttribute(parentAttributes, helpAttributes) ||
|
||||
hasAnyAttribute(scopedAncestorAttributes, helpAttributes);
|
||||
const hasExactHelp = hasAnyAttribute(attributes, exactHelpAttributes) ||
|
||||
hasAnyAttribute(parentAttributes, exactHelpAttributes) ||
|
||||
hasAnyAttribute(scopedAncestorAttributes, exactHelpAttributes);
|
||||
const helpContextId = firstAttribute(attributes, exactHelpAttributes) ??
|
||||
firstAttribute(parentAttributes, exactHelpAttributes) ??
|
||||
firstAttribute(scopedAncestorAttributes, exactHelpAttributes);
|
||||
const explicitId = firstAttribute(
|
||||
attributes,
|
||||
new Set(["interfaceId", "data-interface-id", "id", "name", "field"])
|
||||
);
|
||||
const context = nearestNamedContext(node);
|
||||
const risk = helpRiskFor({
|
||||
component,
|
||||
context,
|
||||
file: relativeFile,
|
||||
label,
|
||||
explicitId,
|
||||
name: attributes.get("name") ?? attributes.get("id") ?? attributes.get("field") ?? null,
|
||||
explicitRisk: firstAttribute(attributes, helpRiskAttributes) ??
|
||||
firstAttribute(parentAttributes, helpRiskAttributes) ??
|
||||
firstAttribute(scopedAncestorAttributes, helpRiskAttributes)
|
||||
});
|
||||
const riskReviewed = firstAttribute(attributes, reviewedHelpRiskAttributes) ??
|
||||
firstAttribute(parentAttributes, reviewedHelpRiskAttributes) ??
|
||||
firstAttribute(scopedAncestorAttributes, reviewedHelpRiskAttributes);
|
||||
const stableId = sourceIdentity(
|
||||
"field",
|
||||
node,
|
||||
@@ -235,7 +282,14 @@ function inspectSource(repository, sourceRoot, sourcePath) {
|
||||
help: help ?? null,
|
||||
helpId: hasHelp ? `${stableId}.help` : null,
|
||||
helpDynamic: hasHelp && help === null,
|
||||
helpCandidate: !hasHelp
|
||||
helpCandidate: !hasHelp,
|
||||
helpExact: hasExactHelp,
|
||||
helpContextId,
|
||||
helpContextDynamic: hasExactHelp && helpContextId === null,
|
||||
helpRisk: risk.value,
|
||||
helpRiskSource: risk.source,
|
||||
helpRiskReviewed: riskReviewed,
|
||||
highRiskHelpMissing: risk.value !== null && !hasExactHelp && riskReviewed !== "standard"
|
||||
});
|
||||
|
||||
}
|
||||
@@ -259,6 +313,19 @@ function inspectSource(repository, sourceRoot, sourcePath) {
|
||||
new Set(["interfaceId", "data-interface-id", "id", "name"])
|
||||
);
|
||||
const context = nearestNamedContext(node);
|
||||
const hasHelp = hasAnyAttribute(attributes, helpAttributes);
|
||||
const hasExactHelp = hasAnyAttribute(attributes, exactHelpAttributes);
|
||||
const helpContextId = firstAttribute(attributes, exactHelpAttributes);
|
||||
const risk = helpRiskFor({
|
||||
component,
|
||||
context,
|
||||
file: relativeFile,
|
||||
label,
|
||||
explicitId,
|
||||
name: attributes.get("name") ?? attributes.get("id") ?? null,
|
||||
explicitRisk: firstAttribute(attributes, helpRiskAttributes)
|
||||
});
|
||||
const riskReviewed = firstAttribute(attributes, reviewedHelpRiskAttributes);
|
||||
result.actions.push({
|
||||
...locate(node),
|
||||
id: sourceIdentity(
|
||||
@@ -271,7 +338,15 @@ function inspectSource(repository, sourceRoot, sourcePath) {
|
||||
idSource: explicitId === null ? "source_anchor" : "explicit",
|
||||
context,
|
||||
component,
|
||||
label
|
||||
label,
|
||||
helpExact: hasExactHelp,
|
||||
helpContextId,
|
||||
helpContextDynamic: hasExactHelp && helpContextId === null,
|
||||
helpDynamic: hasHelp && firstAttribute(attributes, helpAttributes) === null,
|
||||
helpRisk: risk.value,
|
||||
helpRiskSource: risk.source,
|
||||
helpRiskReviewed: riskReviewed,
|
||||
highRiskHelpMissing: risk.value !== null && !hasExactHelp && riskReviewed !== "standard"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,6 +427,26 @@ function inspectSource(repository, sourceRoot, sourcePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function nearestScopedHelpAttributes(node) {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
if (ts.isJsxElement(current)) {
|
||||
const attributes = jsxAttributes(current.openingElement);
|
||||
if (attributes.get("data-help-scope") === "field") return attributes;
|
||||
}
|
||||
if (
|
||||
ts.isFunctionDeclaration(current) ||
|
||||
ts.isMethodDeclaration(current) ||
|
||||
ts.isArrowFunction(current) ||
|
||||
ts.isFunctionExpression(current)
|
||||
) {
|
||||
return new Map();
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return new Map();
|
||||
}
|
||||
|
||||
function jsxAttributes(node) {
|
||||
const mapped = new Map();
|
||||
for (const attribute of node.attributes.properties) {
|
||||
@@ -577,6 +672,34 @@ function hasAnyAttribute(attributes, names) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function helpRiskFor({ component, context, file, label, explicitId, name, explicitRisk }) {
|
||||
if (typeof explicitRisk === "string") {
|
||||
return supportedHelpRisks.has(explicitRisk)
|
||||
? { value: explicitRisk, source: "explicit" }
|
||||
: { value: null, source: "invalid_explicit" };
|
||||
}
|
||||
const value = [component, context, file, label, explicitId, name]
|
||||
.filter((item) => typeof item === "string")
|
||||
.join(" ")
|
||||
.toLowerCase()
|
||||
.replace(/^i18n:/g, "")
|
||||
.replace(/[._-]+/g, " ");
|
||||
const patterns = [
|
||||
["irreversible", /\b(delete|destroy|erase|purge|dispose|disposition|revoke|withdraw|shred)\b/],
|
||||
["credential", /\b(credential|password|secret|token|api key|private key)\b/],
|
||||
["retention", /\b(retention|legal hold|archive lifecycle)\b/],
|
||||
["encryption", /\b(encrypt|encryption|decrypt|decryption|signing key|signature key)\b/],
|
||||
["disclosure", /\b(disclose|disclosure|publish|share externally|public export)\b/],
|
||||
["external-effect", /\b(send|deliver|transfer|refund|payment execution|webhook execution)\b/],
|
||||
["authority", /\b(grant permission|role assignment|approve|reject|formal decision|mandate)\b/],
|
||||
["policy", /\b(policy apply|policy override|enforcement mode)\b/]
|
||||
];
|
||||
for (const [risk, pattern] of patterns) {
|
||||
if (pattern.test(value)) return { value: risk, source: "inferred" };
|
||||
}
|
||||
return { value: null, source: null };
|
||||
}
|
||||
|
||||
function slug(value) {
|
||||
const normalized = value
|
||||
.toLowerCase()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"maximum_missing_exact_help": 0,
|
||||
"rationale": "The source-derived high-risk queue for Core issue #284 is fully resolved. Strict declarations reject any new high-risk control without an exact, manifest-declared, German-complete F1 context."
|
||||
}
|
||||
@@ -31,6 +31,9 @@ ENDPOINT_SURFACE_CATEGORIES = {
|
||||
DEFAULT_ENDPOINT_DECLARATIONS = (
|
||||
META_ROOT / "tools" / "inventory" / "endpoint-surface-declarations.json"
|
||||
)
|
||||
DEFAULT_HIGH_RISK_HELP_BASELINE = (
|
||||
META_ROOT / "tools" / "inventory" / "high-risk-help-baseline.json"
|
||||
)
|
||||
REQUIRED_LOCALES = ("de", "en")
|
||||
REFERENCE_LOCALE = "de"
|
||||
|
||||
@@ -75,6 +78,12 @@ def main() -> int:
|
||||
default=DEFAULT_ENDPOINT_DECLARATIONS,
|
||||
help="Versioned endpoint-surface declaration registry.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--high-risk-help-baseline",
|
||||
type=Path,
|
||||
default=DEFAULT_HIGH_RISK_HELP_BASELINE,
|
||||
help="Versioned upper bound for high-risk controls without exact F1 help.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
catalog = json.loads((META_ROOT / "repositories.json").read_text(encoding="utf-8"))
|
||||
@@ -85,11 +94,15 @@ def main() -> int:
|
||||
endpoint_declarations = _load_endpoint_declarations(
|
||||
args.endpoint_declarations.resolve()
|
||||
)
|
||||
high_risk_help_baseline = _load_high_risk_help_baseline(
|
||||
args.high_risk_help_baseline.resolve()
|
||||
)
|
||||
inventory = _assemble_inventory(
|
||||
webui=webui,
|
||||
backend_endpoints=backend_endpoints,
|
||||
manifests=manifests,
|
||||
endpoint_declarations=endpoint_declarations,
|
||||
high_risk_help_baseline=high_risk_help_baseline,
|
||||
runtime_snapshot=(
|
||||
_load_runtime_snapshot(args.runtime_snapshot.resolve())
|
||||
if args.runtime_snapshot is not None
|
||||
@@ -164,6 +177,28 @@ def _strict_failures(
|
||||
f"{len(declaration_health['stale_runtime_routes'])} runtime route "
|
||||
"declarations have no WebUI implementation"
|
||||
)
|
||||
help_health = inventory.get("help_health", {})
|
||||
if check_declarations and help_health.get("invalid_risk_annotations"):
|
||||
failures.append(
|
||||
f"{len(help_health['invalid_risk_annotations'])} controls use an "
|
||||
"unsupported contextual-help risk class"
|
||||
)
|
||||
if check_declarations and help_health.get("baseline_regression"):
|
||||
failures.append(
|
||||
f"{len(help_health['missing_exact_high_risk_help'])} high-risk "
|
||||
"controls lack exact F1 help; baseline permits at most "
|
||||
f"{help_health['baseline_maximum_missing']}"
|
||||
)
|
||||
if check_declarations and help_health.get("unresolved_exact_high_risk_help"):
|
||||
failures.append(
|
||||
f"{len(help_health['unresolved_exact_high_risk_help'])} high-risk "
|
||||
"controls reference no manifest DocumentationTopic help context"
|
||||
)
|
||||
if check_declarations and help_health.get("high_risk_help_without_german"):
|
||||
failures.append(
|
||||
f"{len(help_health['high_risk_help_without_german'])} high-risk "
|
||||
"controls resolve to documentation without complete German content"
|
||||
)
|
||||
runtime_comparison = inventory.get("runtime_comparison")
|
||||
if (
|
||||
check_declarations
|
||||
@@ -355,6 +390,29 @@ def _extract_manifests(
|
||||
}
|
||||
for permission in manifest.permissions
|
||||
],
|
||||
"documentation": [
|
||||
{
|
||||
"id": topic.id,
|
||||
"help_contexts": sorted(
|
||||
{
|
||||
str(context)
|
||||
for context in topic.metadata.get(
|
||||
"help_contexts", ()
|
||||
)
|
||||
if isinstance(context, str) and context.strip()
|
||||
}
|
||||
),
|
||||
"german_complete": (
|
||||
isinstance(topic.translations.get("de"), dict)
|
||||
and all(
|
||||
isinstance(topic.translations["de"].get(field), str)
|
||||
and topic.translations["de"][field].strip()
|
||||
for field in ("title", "summary", "body")
|
||||
)
|
||||
),
|
||||
}
|
||||
for topic in manifest.documentation
|
||||
],
|
||||
"architecture": (
|
||||
manifest.architecture.to_dict()
|
||||
if manifest.architecture is not None
|
||||
@@ -400,6 +458,7 @@ def _assemble_inventory(
|
||||
backend_endpoints: list[dict[str, Any]],
|
||||
manifests: list[dict[str, Any]],
|
||||
endpoint_declarations: dict[tuple[str, str, str], dict[str, Any]],
|
||||
high_risk_help_baseline: dict[str, Any] | None = None,
|
||||
runtime_snapshot: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
frontend_refs = webui["frontendApiReferences"]
|
||||
@@ -471,8 +530,47 @@ def _assemble_inventory(
|
||||
if any(key not in catalog_keys.get(locale, set()) for locale in expected_locales)
|
||||
]
|
||||
fields = webui["fields"]
|
||||
actions = webui.get("actions", [])
|
||||
help_candidates = [field for field in fields if field["helpCandidate"]]
|
||||
dynamic_help = [field for field in fields if field.get("helpDynamic")]
|
||||
controls = [*fields, *actions]
|
||||
high_risk_controls = [item for item in controls if item.get("helpRisk")]
|
||||
missing_exact_high_risk_help = [
|
||||
item for item in controls if item.get("highRiskHelpMissing")
|
||||
]
|
||||
invalid_risk_annotations = [
|
||||
item
|
||||
for item in controls
|
||||
if item.get("helpRiskSource") == "invalid_explicit"
|
||||
]
|
||||
documentation_contexts = {
|
||||
context: {
|
||||
"module_id": manifest["id"],
|
||||
"topic_id": topic["id"],
|
||||
"german_complete": topic["german_complete"],
|
||||
}
|
||||
for manifest in manifests
|
||||
for topic in manifest.get("documentation", [])
|
||||
for context in topic.get("help_contexts", [])
|
||||
}
|
||||
unresolved_exact_high_risk_help = [
|
||||
item
|
||||
for item in high_risk_controls
|
||||
if item.get("helpExact")
|
||||
and not item.get("helpContextDynamic")
|
||||
and item.get("helpContextId") not in documentation_contexts
|
||||
]
|
||||
high_risk_help_without_german = [
|
||||
item
|
||||
for item in high_risk_controls
|
||||
if item.get("helpContextId") in documentation_contexts
|
||||
and not documentation_contexts[item["helpContextId"]]["german_complete"]
|
||||
]
|
||||
baseline_maximum_missing = (
|
||||
high_risk_help_baseline["maximum_missing_exact_help"]
|
||||
if high_risk_help_baseline is not None
|
||||
else None
|
||||
)
|
||||
governance_adoption = Counter(
|
||||
dimension["adoption"]
|
||||
for manifest in manifests
|
||||
@@ -499,10 +597,34 @@ def _assemble_inventory(
|
||||
"modules": manifests,
|
||||
"interface_declarations": source_declarations,
|
||||
"declaration_health": declaration_health,
|
||||
"help_health": {
|
||||
"supported_risk_classes": sorted(
|
||||
{
|
||||
str(item["helpRisk"])
|
||||
for item in high_risk_controls
|
||||
if item.get("helpRisk")
|
||||
}
|
||||
),
|
||||
"high_risk_controls": high_risk_controls,
|
||||
"missing_exact_high_risk_help": missing_exact_high_risk_help,
|
||||
"invalid_risk_annotations": invalid_risk_annotations,
|
||||
"unresolved_exact_high_risk_help": unresolved_exact_high_risk_help,
|
||||
"high_risk_help_without_german": high_risk_help_without_german,
|
||||
"dynamic_owner_context_controls": [
|
||||
item
|
||||
for item in high_risk_controls
|
||||
if item.get("helpContextDynamic")
|
||||
],
|
||||
"baseline_maximum_missing": baseline_maximum_missing,
|
||||
"baseline_regression": (
|
||||
baseline_maximum_missing is not None
|
||||
and len(missing_exact_high_risk_help) > baseline_maximum_missing
|
||||
),
|
||||
},
|
||||
"runtime_comparison": runtime_comparison,
|
||||
"ui": {
|
||||
"fields": fields,
|
||||
"actions": webui.get("actions", []),
|
||||
"actions": actions,
|
||||
"labels": webui["labels"],
|
||||
"visible_text": webui["visibleText"],
|
||||
"routes": webui["routes"],
|
||||
@@ -554,7 +676,19 @@ def _assemble_inventory(
|
||||
"ui_fields_with_resolvable_f1_context": len(fields),
|
||||
"help_review_candidates": len(help_candidates),
|
||||
"dynamic_help_references": len(dynamic_help),
|
||||
"ui_actions": len(webui.get("actions", [])),
|
||||
"ui_actions": len(actions),
|
||||
"high_risk_controls": len(high_risk_controls),
|
||||
"high_risk_controls_with_exact_help": (
|
||||
len(high_risk_controls) - len(missing_exact_high_risk_help)
|
||||
),
|
||||
"high_risk_controls_missing_exact_help": len(
|
||||
missing_exact_high_risk_help
|
||||
),
|
||||
"invalid_help_risk_annotations": len(invalid_risk_annotations),
|
||||
"unresolved_exact_high_risk_help": len(
|
||||
unresolved_exact_high_risk_help
|
||||
),
|
||||
"high_risk_help_without_german": len(high_risk_help_without_german),
|
||||
"interface_declarations": len(source_declarations),
|
||||
"duplicate_interface_ids": len(declaration_health["duplicate_ids"]),
|
||||
"undeclared_source_surfaces": len(
|
||||
@@ -885,6 +1019,10 @@ def _render_markdown(inventory: dict[str, Any]) -> str:
|
||||
for item in inventory["api"]["unreferenced_by_static_webui_scan"]
|
||||
)
|
||||
classification_counts = inventory["api"]["classification_counts"]
|
||||
high_risk_by_repository = Counter(
|
||||
item["repository"]
|
||||
for item in inventory["help_health"]["missing_exact_high_risk_help"]
|
||||
)
|
||||
lines = [
|
||||
"# GovOPlaN Platform Interface Inventory",
|
||||
"",
|
||||
@@ -900,6 +1038,12 @@ def _render_markdown(inventory: dict[str, Any]) -> str:
|
||||
f"- Fields with a resolvable F1 context: {summary['ui_fields_with_resolvable_f1_context']}",
|
||||
f"- Fields with dynamic help references: {summary['dynamic_help_references']}",
|
||||
f"- Help review candidates: {summary['help_review_candidates']}",
|
||||
f"- High-risk controls: {summary['high_risk_controls']}",
|
||||
f"- High-risk controls with exact F1 help: {summary['high_risk_controls_with_exact_help']}",
|
||||
f"- High-risk controls missing exact F1 help: {summary['high_risk_controls_missing_exact_help']}",
|
||||
f"- Invalid help-risk annotations: {summary['invalid_help_risk_annotations']}",
|
||||
f"- High-risk exact contexts missing a manifest topic: {summary['unresolved_exact_high_risk_help']}",
|
||||
f"- High-risk contexts without complete German topic content: {summary['high_risk_help_without_german']}",
|
||||
f"- Stable interface declarations: {summary['interface_declarations']}",
|
||||
f"- Duplicate interface IDs: {summary['duplicate_interface_ids']}",
|
||||
f"- WebUI surfaces missing runtime declarations: {summary['undeclared_source_surfaces']}",
|
||||
@@ -930,6 +1074,23 @@ def _render_markdown(inventory: dict[str, Any]) -> str:
|
||||
f"| `{repository}` | {count} |"
|
||||
for repository, count in sorted(help_by_repository.items())
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## High-risk Contextual-help Debt",
|
||||
"",
|
||||
"Inferred or explicitly classified high-risk controls require an exact",
|
||||
"F1 context. `data-help-risk-reviewed=\"standard\"` records a reviewed",
|
||||
"false positive. The versioned baseline makes this queue non-regressing.",
|
||||
"",
|
||||
"| Repository | Missing exact contexts |",
|
||||
"| --- | ---: |",
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
f"| `{repository}` | {count} |"
|
||||
for repository, count in sorted(high_risk_by_repository.items())
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
@@ -1005,6 +1166,29 @@ def endpoint_key(endpoint: dict[str, Any]) -> tuple[str, str, str]:
|
||||
)
|
||||
|
||||
|
||||
def _load_high_risk_help_baseline(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError as exc:
|
||||
raise ValueError(
|
||||
f"High-risk contextual-help baseline does not exist: {path}"
|
||||
) from exc
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
f"High-risk contextual-help baseline is invalid JSON: {exc}"
|
||||
) from exc
|
||||
if not isinstance(payload, dict) or payload.get("schema_version") != 1:
|
||||
raise ValueError(
|
||||
"High-risk contextual-help baseline must use schema_version 1."
|
||||
)
|
||||
maximum = payload.get("maximum_missing_exact_help")
|
||||
if not isinstance(maximum, int) or isinstance(maximum, bool) or maximum < 0:
|
||||
raise ValueError(
|
||||
"High-risk contextual-help baseline maximum must be a non-negative integer."
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
def _load_endpoint_declarations(
|
||||
path: Path,
|
||||
) -> dict[tuple[str, str, str], dict[str, Any]]:
|
||||
|
||||
@@ -571,6 +571,10 @@ run_manifest_shape_gate() {
|
||||
"$META_ROOT/tools/checks/check-manifest-shapes.py" \
|
||||
--workspace-root "$PARENT" \
|
||||
--require-architecture
|
||||
run env PYTHONDONTWRITEBYTECODE=1 \
|
||||
"$PYTHON" \
|
||||
"$META_ROOT/tools/checks/check-dsar-coverage.py" \
|
||||
--workspace-root "$PARENT"
|
||||
}
|
||||
|
||||
run_migration_release_audit() {
|
||||
|
||||
Reference in New Issue
Block a user