Release v0.1.16
Module Package Release / publish-packages (push) Successful in 12s

This commit is contained in:
2026-08-05 19:53:46 +02:00
parent add7a99f6d
commit f1a5be2a93
35 changed files with 1467 additions and 195 deletions
@@ -0,0 +1,23 @@
from __future__ import annotations
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
_path = (
Path(__file__).resolve().parents[1]
/ "versions"
/ "a36d8e4f9b12_german_reference_locale.py"
)
_spec = spec_from_file_location("govoplan_german_reference_locale_migration", _path)
if _spec is None or _spec.loader is None:
raise RuntimeError(f"Unable to load migration implementation from {_path}")
_module = module_from_spec(_spec)
_spec.loader.exec_module(_module)
revision = _module.revision
down_revision = _module.down_revision
branch_labels = _module.branch_labels
depends_on = _module.depends_on
upgrade = _module.upgrade
downgrade = _module.downgrade
@@ -0,0 +1,44 @@
"""adopt German as the untouched system reference locale
Revision ID: a36d8e4f9b12
Revises: f25c9d3e7a01
Create Date: 2026-08-05 00:00:00.000000
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "a36d8e4f9b12"
down_revision = "f25c9d3e7a01"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
if "core_system_settings" not in set(sa.inspect(bind).get_table_names()):
return
settings = sa.table(
"core_system_settings",
sa.column("id", sa.String),
sa.column("default_locale", sa.String),
sa.column("created_at", sa.DateTime(timezone=True)),
sa.column("updated_at", sa.DateTime(timezone=True)),
)
bind.execute(
settings.update()
.where(settings.c.id == "global")
.where(settings.c.default_locale == "en")
.where(settings.c.created_at == settings.c.updated_at)
.values(default_locale="de", updated_at=sa.func.now())
)
def downgrade() -> None:
# Locale selection is user-visible state. A downgrade must not overwrite a
# German value that may have been selected explicitly after this migration.
pass
+6
View File
@@ -28,6 +28,12 @@ manifest, allowing the shell to link directly to an exact topic when possible.
Docs still performs the authoritative audience, permission, configured-state,
and documentation-type filtering.
Core also maps explicit route, navigation, settings, and View surface IDs to
the module's static user or administrator documentation baseline. This makes a
page association complete by default and gives every derived field/action
context a useful fallback. Exact `metadata.help_contexts` remain the preferred
authoring mechanism for consequential or unfamiliar controls.
When there is no exact topic, Docs resolves the page fallback and then the first
visible topic owned by the module. If Docs is unavailable, the shell opens the
hosted documentation with the same context parameters.
+4
View File
@@ -18,7 +18,9 @@ operator, and roadmap pages.
| External references and integration maturity | `EXTERNAL_REFERENCES_AND_INTEGRATION_MATURITY.md` | Stable external identity and cumulative connector maturity; configured source authority is defined by the meta target architecture. |
| Institutional context and governed references | `INSTITUTIONAL_CONTEXT_CONTRACT.md` | Shared temporal, actor/representation, institution, mandate, service, party, decision, evidence, legal-basis, information-governance, presentation, and geo DTO/provider contracts. |
| Temporal data read context | `TEMPORAL_DATA_CONTEXT.md` | Valid-time and recorded-time titlebar selection, HTTP/cache contract, security boundary, and module-adoption rule. |
| Cross-module information governance adoption | `INFORMATION_GOVERNANCE_ADOPTION.md` | Manifest evidence and enforcement rules for temporal browsing, purpose-aware access, retention, and institutional context. |
| Context-sensitive F1 help | `CONTEXTUAL_HELP_CONTRACT.md` | Focus, route, module-manifest documentation contexts, Docs projection, and hosted fallback. |
| German localization and help quality gate | `LOCALIZATION_AND_HELP_QUALITY.md` | German reference locale, new-installation default, catalog completeness, automatic page associations, and explicit-help review priorities. |
| Postbox E2EE target architecture | `POSTBOX_E2EE_ARCHITECTURE.md` | Strategic encrypted postbox/mailbox model, key ownership, role mailbox semantics, and retraction limits. |
| Shared state, runtime coordination, and recovery | `STATE_AND_RECOVERY_CONTRACT.md` | State profiles, object storage, node registration/drain, fenced leases, migration ordering, and recovery evidence. |
| Module lifecycle recovery | `MODULE_LIFECYCLE_RECOVERY.md` | Installer/live-graph recovery modes, deployment fence, evidence, retry blocking, and operator reconciliation. |
@@ -39,6 +41,8 @@ operator, and roadmap pages.
| Topic | Canonical document | Notes |
| --- | --- | --- |
| Product roadmap and module routing | `GOVOPLAN_MASTER_ROADMAP.md` | Product-level sequencing, implementation gates, issue routing, and missing-module decisions. |
| Stable platform ideas | `govoplan/docs/PLATFORM_CORE_IDEAS.md` | Cross-product thesis, canonical distinctions, product experience rule, maturity rule, and decision test. |
| Current cross-product reconciliation | `govoplan/docs/STRATEGY_STATUS.md` | The only current prose status source; generated evidence and Gitea remain authoritative inputs. |
| Institutional governance target | `govoplan/docs/INSTITUTIONAL_GOVERNANCE_TARGET_ARCHITECTURE.md` | Cross-product semantic layers, source-authority modes, candidate Mandates/Services/Parties/Decisions boundaries, and migration sequence. |
| UI/UX decisions | `UI_UX_DECISION_LEDGER.md` | Binding guided-UI decisions, open decisions, impact index, and review checklist. |
| Core interface migration | `INTERFACE_PATTERN_MIGRATION.md` | Core-owned settings, credential, retention, lifecycle, and shared-component evidence for the product pattern language. |
+131
View File
@@ -0,0 +1,131 @@
# Information Governance Adoption
## Platform Rule
Temporal browsing, purpose-aware access, retention, and institutional context
are platform-wide information-governance dimensions. Every module receives the
same contract by default. A module may claim `partial` or `enforced` only with
repository-owned object scope, evidence, and limitations; it may claim
`not_applicable` only when the dimension genuinely does not apply.
Historical business data is always authorized under the current security
state. No module may use a historical permission, membership, role, function
assignment, or policy projection to weaken present-day access.
Platform-wide adoption is tracked in
[GovOPlaN #40](https://git.add-ideas.de/GovOPlaN/govoplan/issues/40), with
temporal reads detailed in
[GovOPlaN #39](https://git.add-ideas.de/GovOPlaN/govoplan/issues/39).
## Manifest Declaration
`ModuleManifest.information_governance` publishes four dimensions:
- `temporal_browsing`;
- `purpose_aware_access`;
- `retention`;
- `institutional_context`.
Each dimension declares:
- adoption: `not_applicable`, `contract_only`, `partial`, or `enforced`;
- object types covered;
- repository-local test/documentation evidence;
- the remaining limitation for `contract_only` or `partial`.
The default is intentionally `contract_only`. It applies the platform rule
without pretending that existing domain queries and effects already enforce
it. `reference_ready`, `supported`, and `lts` modules cannot retain an
applicable dimension below `enforced`.
## Read Contract
For every persistent domain object, the owner classifies the read:
1. **Current-only:** historical semantics do not exist and the API says so.
2. **Valid-time:** select facts effective now or at the requested instant.
3. **Bitemporal:** additionally select only revisions known by `recorded_at`.
4. **All-validity:** return effective revisions in a bounded history view.
The Core temporal middleware supplies the request context. Owners apply it in
repositories or query helpers, include it in cache keys, return evaluated
context, and test current/at/all plus recorded-time boundaries. Search,
reporting, exports, selectors, counts, and drill-through must use the same
projection as the owning list/detail API.
## Purpose-Aware Access Contract
Permission establishes a technical action ceiling. Purpose-aware access asks
whether this actor, represented capacity, case/work item, legal basis, and
declared use may access this object now.
- A client-supplied purpose is an assertion, never authority by itself.
- The owner or Policy capability validates the purpose and returns explainable
provenance.
- Sensitive access can require case assignment, mandate, reason capture,
approval, or break-glass evidence.
- Search, selectors, reporting, exports, background jobs, and connectors apply
the same decision.
- Audit records the validated purpose identifier and decision reference, not
unnecessary content.
## Retention Contract
Every persistent object declares an owner, retention class or policy reference,
trigger, start instant, hold behavior, review/disposition action, and evidence.
Retention is not a generic timestamp deletion job.
- Domain owners enumerate and execute their own effects through a typed
retention provider.
- Policy resolves inherited ceilings and simulation.
- Records owns record disposition; Files owns byte/object effects; Audit owns
audit-detail behavior; external providers declare their own effect and
recovery semantics.
- Dry-run, legal hold, exact revision, idempotency, outcome unknown,
reconciliation, correction, and destruction evidence are mandatory for
consequential removal.
## Institutional Context Contract
Consequential objects and effects carry the relevant tenant, institution,
organization unit, function, mandate/jurisdiction, service/case/work item,
party/representation, decision, and record references. Context is minimized to
what the operation needs. Organizational membership is not itself permission
or mandate.
Events, automation intents, audit evidence, records, and external effects retain
the same governed context envelope or an exact reference to it. Consumers must
not reconstruct authority later from mutable current structures.
## Adoption Order
1. Inventory every domain list/detail/search/export/effect and classify all
four dimensions.
2. Migrate institutional owners first: Access, IDM, Organizations, Mandates,
Services, Parties, Cases, Approvals, Committee, Decisions, Voting, and
Records.
3. Migrate communication and content: Addresses, Distribution Lists, Campaign,
Postbox, Mail, Calendar, Files, Templates, and Forms Runtime.
4. Migrate data projections: Connectors, Datasources, Dataflow, Reporting,
Search, Risk Compliance, and Dashboard.
5. Migrate workflow/task/background/provider operations and prove that no
asynchronous path drops context.
6. Advance manifest claims only after owner tests and browser/reference-journey
evidence pass.
The generated platform inventory reports adoption counts and module details.
Gitea tracks individual migrations; the declaration is evidence and a maturity
gate, not a substitute for implementation.
## Definition Of Enforced
A dimension is `enforced` only when:
- all declared object types and public reads/effects use it;
- list/detail/count/search/export/worker behavior is consistent;
- cache and pagination semantics cannot cross contexts;
- absence, invalid values, and inaccessible referenced context fail safely;
- tests cover current, historical, unauthorized, replay, and module-absence
combinations appropriate to the dimension;
- user/admin documentation explains behavior and limitations;
- the manifest cites those tests and docs.
+60
View File
@@ -0,0 +1,60 @@
# Localization And Contextual Help Quality
## Reference Language
German (`de`) is GovOPlaN's first-class reference target. Every translation key
used by a shipped WebUI must exist in German and English. German completeness is
a release gate; English remains the source-code fallback language so existing
literal labels and external developer APIs do not change semantics.
New installations and tenants default to German. Existing system, tenant, and
user preferences are preserved. The available-language and policy model can
still select another default or disable a package at the relevant scope.
Explicit high-risk help content and browser acceptance are tracked in
[Core #284](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/284).
The platform inventory recognizes both inline locale objects and generated
catalogs declared as `const de` / `const en`. Its strict mode requires both
locales and reports `de` explicitly as the reference locale.
## Help Resolution
Every focusable field and action receives a stable derived F1 identity from the
shared shell, even when the component has no dedicated help text. Resolution
falls back from field/action to dialog or page and then to the module's visible
documentation baseline.
Backend manifests publish explicit topic associations first. Core additionally
associates declared route, navigation, settings, and View surface IDs with the
module's static user or administrator documentation baseline. Feature modules
should still add exact `metadata.help_contexts` entries for consequential,
unfamiliar, policy-controlled, destructive, security-sensitive, or legally
meaningful fields and actions.
The generated `help_review_candidates` list is therefore a content-depth queue,
not a list of controls on which F1 cannot work. It should prioritize:
1. effect, deletion, delivery, retention, disclosure, encryption, and recovery;
2. identity, representation, mandate, institutional context, and purpose;
3. valid-time versus recorded-time selection;
4. provider authority, synchronization, conflict, and outcome unknown;
5. fields whose consequences are not evident from their label.
## Verification
```bash
cd /mnt/DATA/git/govoplan
/mnt/DATA/git/govoplan/.venv/bin/python \
tools/inventory/platform-interface-inventory.py \
--strict --strict-declarations --strict-endpoints
```
The check must report:
- reference locale `de` present and complete;
- no used key missing from `de` or `en`;
- every field has a resolvable F1 context;
- no duplicate stable IDs;
- no undeclared public WebUI surface;
- no stale runtime route or endpoint declaration.
+9 -2
View File
@@ -93,10 +93,17 @@ Alembic, and post-migration tasks. The lock is session-scoped and therefore
released if the migration process dies.
Runtime roles run `govoplan_core.commands.wait_for_database`. It waits until
the database has exactly the configured Core/module Alembic heads and never
upgrades schema. This permits a migration Job and runtime Deployments to be
the database has exactly the configured, dependency-resolved Core/module
Alembic heads and never upgrades schema. Cross-module `depends_on` revisions
therefore do not leave runtime roles waiting for a branch marker Alembic has
correctly consumed. This permits a migration Job and runtime Deployments to be
submitted together while keeping startup fail-closed.
Runtime coordination records the installed `govoplan-core` distribution
version for API, worker and scheduler roles. FastAPI/OpenAPI metadata versions
are presentation metadata and must not be used as deployable software identity;
mixing the two would create a false version-skew readiness failure.
## Recovery Ledger
`govoplan_core.core.recovery` provides a durable operation and evidence
+402
View File
@@ -1065,6 +1065,408 @@
"release": "0.1.15",
"squash_policy": "reviewed-manual",
"track": "release"
},
{
"heads": [
{
"owner": "govoplan-notifications",
"revision": "6e2f91ab4c70"
},
{
"owner": "govoplan-poll",
"revision": "6e7f8a9b0c1d"
},
{
"owner": "govoplan-dashboard",
"revision": "7b9d2f4a6c8e"
},
{
"owner": "govoplan-voting",
"revision": "8b9c0d1e2f3a"
},
{
"owner": "govoplan-mail",
"revision": "93b4c5d6e7f8"
},
{
"owner": "govoplan-core",
"revision": "a36d8e4f9b12"
},
{
"owner": "govoplan-forms-runtime",
"revision": "a3d5f7b9c1e2"
},
{
"owner": "govoplan-templates",
"revision": "a3f7c9d2e1b4"
},
{
"owner": "govoplan-organizations",
"revision": "a61e4d9c72b8"
},
{
"owner": "govoplan-mandates",
"revision": "a8b1c2d3e4f5"
},
{
"owner": "govoplan-audit",
"revision": "a8d1e4f7b2c5"
},
{
"owner": "govoplan-approvals",
"revision": "a91c4e72b5d8"
},
{
"owner": "govoplan-policy",
"revision": "a9c4e7b2d5f8"
},
{
"owner": "govoplan-idm",
"revision": "b1c2d3e4f5a6"
},
{
"owner": "govoplan-search",
"revision": "b2c3d4e5f607"
},
{
"owner": "govoplan-datasources",
"revision": "b8d2f5a0c3e7"
},
{
"owner": "govoplan-views",
"revision": "b8e4c1f7a2d9"
},
{
"owner": "govoplan-risk-compliance",
"revision": "b9c0d1e2f3a4"
},
{
"owner": "govoplan-services",
"revision": "b9c2d3e4f5a6"
},
{
"owner": "govoplan-parties",
"revision": "c0d3e4f5a6b7"
},
{
"owner": "govoplan-identity-trust",
"revision": "c3f5a7b9d1e2"
},
{
"owner": "govoplan-projects",
"revision": "c4a1e8f2d6b9"
},
{
"owner": "govoplan-addresses",
"revision": "c5d7e8f9a0b1"
},
{
"owner": "govoplan-access",
"revision": "c7e0a3d6f9b2"
},
{
"owner": "govoplan-reporting",
"revision": "c8d5e2f6a9b3"
},
{
"owner": "govoplan-scheduling",
"revision": "c9d4e7f1a2b3"
},
{
"owner": "govoplan-decisions",
"revision": "d1e4f5a6b7c8"
},
{
"owner": "govoplan-calendar",
"revision": "d24e5f607182"
},
{
"owner": "govoplan-committee",
"revision": "d8b9f0a1c2e3"
},
{
"owner": "govoplan-postbox",
"revision": "d8e3f6a9b2c5"
},
{
"owner": "govoplan-campaign",
"revision": "e3c8f4a5b6d7"
},
{
"owner": "govoplan-workflow-engine",
"revision": "e4a1f8c2d7b6"
},
{
"owner": "govoplan-encryption",
"revision": "e5b7c9d1f3a4"
},
{
"owner": "govoplan-dist-lists",
"revision": "e7c3a9d1b5f2"
},
{
"owner": "govoplan-files",
"revision": "f1a2b3c4d5e7"
},
{
"owner": "govoplan-dataflow",
"revision": "f6c2a9d4e7b1"
},
{
"owner": "govoplan-cases",
"revision": "f6d3a8b1c4e7"
},
{
"owner": "govoplan-connectors",
"revision": "f7c8d9e0a1b2"
}
],
"owner_heads": [
{
"owner": "govoplan-access",
"revisions": [
"c7e0a3d6f9b2"
]
},
{
"owner": "govoplan-addresses",
"revisions": [
"c5d7e8f9a0b1"
]
},
{
"owner": "govoplan-approvals",
"revisions": [
"a91c4e72b5d8"
]
},
{
"owner": "govoplan-audit",
"revisions": [
"a8d1e4f7b2c5"
]
},
{
"owner": "govoplan-calendar",
"revisions": [
"d24e5f607182"
]
},
{
"owner": "govoplan-campaign",
"revisions": [
"e3c8f4a5b6d7"
]
},
{
"owner": "govoplan-cases",
"revisions": [
"f6d3a8b1c4e7"
]
},
{
"owner": "govoplan-committee",
"revisions": [
"d8b9f0a1c2e3"
]
},
{
"owner": "govoplan-connectors",
"revisions": [
"f7c8d9e0a1b2"
]
},
{
"owner": "govoplan-core",
"revisions": [
"a36d8e4f9b12"
]
},
{
"owner": "govoplan-dashboard",
"revisions": [
"7b9d2f4a6c8e"
]
},
{
"owner": "govoplan-dataflow",
"revisions": [
"f6c2a9d4e7b1"
]
},
{
"owner": "govoplan-datasources",
"revisions": [
"b8d2f5a0c3e7"
]
},
{
"owner": "govoplan-decisions",
"revisions": [
"d1e4f5a6b7c8"
]
},
{
"owner": "govoplan-dist-lists",
"revisions": [
"e7c3a9d1b5f2"
]
},
{
"owner": "govoplan-encryption",
"revisions": [
"e5b7c9d1f3a4"
]
},
{
"owner": "govoplan-files",
"revisions": [
"f1a2b3c4d5e7"
]
},
{
"owner": "govoplan-forms",
"revisions": [
"e1f2a3b4c5d6"
]
},
{
"owner": "govoplan-forms-runtime",
"revisions": [
"a3d5f7b9c1e2"
]
},
{
"owner": "govoplan-identity",
"revisions": [
"5c6d7e8f9a10"
]
},
{
"owner": "govoplan-identity-trust",
"revisions": [
"c3f5a7b9d1e2"
]
},
{
"owner": "govoplan-idm",
"revisions": [
"b1c2d3e4f5a6"
]
},
{
"owner": "govoplan-mail",
"revisions": [
"93b4c5d6e7f8"
]
},
{
"owner": "govoplan-mandates",
"revisions": [
"a8b1c2d3e4f5"
]
},
{
"owner": "govoplan-notifications",
"revisions": [
"6e2f91ab4c70"
]
},
{
"owner": "govoplan-organizations",
"revisions": [
"a61e4d9c72b8"
]
},
{
"owner": "govoplan-parties",
"revisions": [
"c0d3e4f5a6b7"
]
},
{
"owner": "govoplan-policy",
"revisions": [
"a9c4e7b2d5f8"
]
},
{
"owner": "govoplan-poll",
"revisions": [
"6e7f8a9b0c1d"
]
},
{
"owner": "govoplan-postbox",
"revisions": [
"d8e3f6a9b2c5"
]
},
{
"owner": "govoplan-projects",
"revisions": [
"c4a1e8f2d6b9"
]
},
{
"owner": "govoplan-reporting",
"revisions": [
"c8d5e2f6a9b3"
]
},
{
"owner": "govoplan-risk-compliance",
"revisions": [
"b9c0d1e2f3a4"
]
},
{
"owner": "govoplan-scheduling",
"revisions": [
"c9d4e7f1a2b3"
]
},
{
"owner": "govoplan-search",
"revisions": [
"b2c3d4e5f607"
]
},
{
"owner": "govoplan-services",
"revisions": [
"b9c2d3e4f5a6"
]
},
{
"owner": "govoplan-templates",
"revisions": [
"a3f7c9d2e1b4"
]
},
{
"owner": "govoplan-views",
"revisions": [
"b8e4c1f7a2d9"
]
},
{
"owner": "govoplan-voting",
"revisions": [
"8b9c0d1e2f3a"
]
},
{
"owner": "govoplan-workflow-engine",
"revisions": [
"e4a1f8c2d7b6"
]
}
],
"recorded_at": "2026-08-05T17:51:25Z",
"release": "0.1.16",
"squash_policy": "reviewed-manual",
"track": "release"
}
],
"version": 1
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-core"
version = "0.1.15"
version = "0.1.16"
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
readme = "README.md"
requires-python = ">=3.12"
+1 -2
View File
@@ -12,7 +12,7 @@ class SystemSettings(Base, TimestampMixin):
__tablename__ = "core_system_settings"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default="global")
default_locale: Mapped[str] = mapped_column(String(20), default="en", nullable=False)
default_locale: Mapped[str] = mapped_column(String(20), default="de", nullable=False)
allow_tenant_custom_groups: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
allow_tenant_custom_roles: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
allow_tenant_api_keys: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
@@ -20,4 +20,3 @@ class SystemSettings(Base, TimestampMixin):
__all__ = ["SystemSettings"]
+4 -4
View File
@@ -84,7 +84,7 @@ class TenantInfo(BaseModel):
slug: str
name: str
is_active: bool = True
default_locale: str = "en"
default_locale: str = "de"
enabled_language_codes: list[str] = Field(default_factory=list)
@@ -210,7 +210,7 @@ class AuthProfileResponse(BaseModel):
active_tenant: TenantInfo
available_languages: list[LanguageInfo] = Field(default_factory=list)
enabled_language_codes: list[str] = Field(default_factory=list)
default_language: str = "en"
default_language: str = "de"
profile_loaded: bool = True
@@ -239,7 +239,7 @@ class LoginResponse(BaseModel):
principal: PrincipalContextInfo | None = None
available_languages: list[LanguageInfo] = Field(default_factory=list)
enabled_language_codes: list[str] = Field(default_factory=list)
default_language: str = "en"
default_language: str = "de"
profile_loaded: bool = True
roles_loaded: bool = True
groups_loaded: bool = True
@@ -257,7 +257,7 @@ class MeResponse(BaseModel):
principal: PrincipalContextInfo | None = None
available_languages: list[LanguageInfo] = Field(default_factory=list)
enabled_language_codes: list[str] = Field(default_factory=list)
default_language: str = "en"
default_language: str = "de"
profile_loaded: bool = True
roles_loaded: bool = True
groups_loaded: bool = True
+1 -1
View File
@@ -356,7 +356,7 @@ def consume_first_admin_credential(
tenant = Tenant(
slug=clean_tenant_slug,
name=clean_tenant_name,
default_locale="en",
default_locale="de",
settings={},
is_active=True,
)
@@ -0,0 +1,236 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal, Mapping, cast
InformationGovernanceAdoption = Literal[
"not_applicable",
"contract_only",
"partial",
"enforced",
]
INFORMATION_GOVERNANCE_ADOPTION_ORDER: tuple[InformationGovernanceAdoption, ...] = (
"not_applicable",
"contract_only",
"partial",
"enforced",
)
class InformationGovernanceDeclarationError(ValueError):
pass
@dataclass(frozen=True, slots=True)
class InformationGovernanceDimension:
"""Truthful module-level adoption claim for one cross-cutting dimension."""
adoption: InformationGovernanceAdoption = "contract_only"
object_types: tuple[str, ...] = ()
evidence: tuple[str, ...] = ()
limitation: str | None = None
def __post_init__(self) -> None:
if self.adoption not in INFORMATION_GOVERNANCE_ADOPTION_ORDER:
raise InformationGovernanceDeclarationError(
f"Unsupported information-governance adoption: {self.adoption!r}."
)
for field_name in ("object_types", "evidence"):
values = getattr(self, field_name)
if len(values) != len(set(values)) or any(not item.strip() for item in values):
raise InformationGovernanceDeclarationError(
f"Information-governance {field_name.replace('_', ' ')} must "
"contain unique non-empty values."
)
if self.adoption == "enforced" and not self.evidence:
raise InformationGovernanceDeclarationError(
"An enforced information-governance dimension requires evidence."
)
if self.adoption in {"partial", "enforced"} and not self.object_types:
raise InformationGovernanceDeclarationError(
"Partial and enforced information-governance dimensions must "
"name their covered object types."
)
if self.adoption == "not_applicable" and self.object_types:
raise InformationGovernanceDeclarationError(
"A non-applicable information-governance dimension cannot declare object types."
)
if self.adoption in {"contract_only", "partial"} and not str(
self.limitation or ""
).strip():
raise InformationGovernanceDeclarationError(
"Contract-only and partial adoption must state the current limitation."
)
def to_dict(self) -> dict[str, object]:
return {
"adoption": self.adoption,
"object_types": list(self.object_types),
"evidence": list(self.evidence),
"limitation": self.limitation,
}
def _contract_only_dimension() -> InformationGovernanceDimension:
return InformationGovernanceDimension(
adoption="contract_only",
limitation=(
"The platform contract applies, but module-specific adoption evidence "
"has not been declared."
),
)
@dataclass(frozen=True, slots=True)
class ModuleInformationGovernance:
"""Cross-cutting data-use requirements and honest adoption evidence."""
temporal_browsing: InformationGovernanceDimension = field(
default_factory=_contract_only_dimension
)
purpose_aware_access: InformationGovernanceDimension = field(
default_factory=_contract_only_dimension
)
retention: InformationGovernanceDimension = field(
default_factory=_contract_only_dimension
)
institutional_context: InformationGovernanceDimension = field(
default_factory=_contract_only_dimension
)
current_authorization_for_historical_reads: bool = True
contract_version: str = "1"
def __post_init__(self) -> None:
if self.contract_version != "1":
raise InformationGovernanceDeclarationError(
"Unsupported module information-governance contract version."
)
if not self.current_authorization_for_historical_reads:
raise InformationGovernanceDeclarationError(
"Historical reads must always use current authorization."
)
for name, dimension in self.dimensions.items():
if not isinstance(dimension, InformationGovernanceDimension):
raise InformationGovernanceDeclarationError(
f"Information-governance dimension {name!r} has an invalid value."
)
@property
def dimensions(self) -> Mapping[str, InformationGovernanceDimension]:
return {
"temporal_browsing": self.temporal_browsing,
"purpose_aware_access": self.purpose_aware_access,
"retention": self.retention,
"institutional_context": self.institutional_context,
}
def to_dict(self) -> dict[str, object]:
return {
"contract_version": self.contract_version,
"current_authorization_for_historical_reads": (
self.current_authorization_for_historical_reads
),
"dimensions": {
name: dimension.to_dict()
for name, dimension in self.dimensions.items()
},
}
def information_governance_from_mapping(
value: Mapping[str, object],
) -> ModuleInformationGovernance:
raw_dimensions = value.get("dimensions")
if not isinstance(raw_dimensions, Mapping):
raise InformationGovernanceDeclarationError(
"Information-governance dimensions must be an object."
)
def dimension(name: str) -> InformationGovernanceDimension:
raw_dimension = raw_dimensions.get(name)
if not isinstance(raw_dimension, Mapping):
raise InformationGovernanceDeclarationError(
f"Information-governance dimension {name!r} must be an object."
)
def text_tuple(field_name: str) -> tuple[str, ...]:
raw_values = raw_dimension.get(field_name, ())
if not isinstance(raw_values, (list, tuple)):
raise InformationGovernanceDeclarationError(
f"Information-governance {name}.{field_name} must be a list."
)
if any(not isinstance(item, str) for item in raw_values):
raise InformationGovernanceDeclarationError(
f"Information-governance {name}.{field_name} must contain strings."
)
return tuple(raw_values)
raw_limitation = raw_dimension.get("limitation")
raw_adoption = raw_dimension.get("adoption") or "contract_only"
if not isinstance(raw_adoption, str):
raise InformationGovernanceDeclarationError(
f"Information-governance {name}.adoption must be a string."
)
if raw_limitation is not None and not isinstance(raw_limitation, str):
raise InformationGovernanceDeclarationError(
f"Information-governance {name}.limitation must be a string."
)
return InformationGovernanceDimension(
adoption=cast(
InformationGovernanceAdoption,
raw_adoption,
),
object_types=text_tuple("object_types"),
evidence=text_tuple("evidence"),
limitation=raw_limitation,
)
current_authorization = value.get(
"current_authorization_for_historical_reads",
True,
)
if not isinstance(current_authorization, bool):
raise InformationGovernanceDeclarationError(
"current_authorization_for_historical_reads must be boolean."
)
return ModuleInformationGovernance(
contract_version=str(value.get("contract_version") or "1"),
current_authorization_for_historical_reads=current_authorization,
temporal_browsing=dimension("temporal_browsing"),
purpose_aware_access=dimension("purpose_aware_access"),
retention=dimension("retention"),
institutional_context=dimension("institutional_context"),
)
def information_governance_maturity_issues(
declaration: ModuleInformationGovernance,
*,
maturity: str | None,
) -> tuple[str, ...]:
if maturity not in {"reference_ready", "supported", "lts"}:
return ()
incomplete = [
name
for name, dimension in declaration.dimensions.items()
if dimension.adoption not in {"not_applicable", "enforced"}
]
if not incomplete:
return ()
return (
f"Maturity {maturity!r} requires enforced or explicitly non-applicable "
"information governance for: " + ", ".join(incomplete),
)
__all__ = [
"INFORMATION_GOVERNANCE_ADOPTION_ORDER",
"InformationGovernanceAdoption",
"InformationGovernanceDeclarationError",
"InformationGovernanceDimension",
"ModuleInformationGovernance",
"information_governance_from_mapping",
"information_governance_maturity_issues",
]
@@ -17,6 +17,10 @@ from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from govoplan_core.core.versioning import format_version_range, version_range_is_valid, version_satisfies_range
from govoplan_core.core.information_governance import (
information_governance_from_mapping,
information_governance_maturity_issues,
)
from govoplan_core.core.provider_governance import (
external_provider_from_mapping,
module_architecture_from_mapping,
@@ -630,6 +634,7 @@ def _normalize_catalog_item(value: Any) -> dict[str, object]:
"tags": _string_list(value.get("tags")),
}
raw_architecture = value.get("architecture")
architecture_maturity: str | None = None
if raw_architecture is not None:
if not isinstance(raw_architecture, Mapping):
raise ValueError(
@@ -647,6 +652,27 @@ def _normalize_catalog_item(value: Any) -> dict[str, object]:
+ "; ".join(issues)
)
item["architecture"] = architecture.to_dict()
architecture_maturity = architecture.maturity
raw_information_governance = value.get("information_governance")
if raw_information_governance is not None:
if not isinstance(raw_information_governance, Mapping):
raise ValueError(
"Module package catalog information_governance for "
f"{module_id!r} must be an object."
)
information_governance = information_governance_from_mapping(
raw_information_governance
)
governance_issues = information_governance_maturity_issues(
information_governance,
maturity=architecture_maturity,
)
if governance_issues:
raise ValueError(
"Module package catalog information_governance for "
f"{module_id!r} is invalid: " + "; ".join(governance_issues)
)
item["information_governance"] = information_governance.to_dict()
raw_providers = value.get("external_providers")
if raw_providers is not None:
if not isinstance(raw_providers, list):
+4
View File
@@ -4,6 +4,7 @@ from collections.abc import Callable, Iterable, Mapping, Sequence
from dataclasses import dataclass, field
from typing import Any, Literal, Protocol, TYPE_CHECKING
from govoplan_core.core.information_governance import ModuleInformationGovernance
from govoplan_core.core.ownership import OwnershipProviderRegistration
from govoplan_core.core.provider_governance import (
ExternalProviderDeclaration,
@@ -446,6 +447,9 @@ class ModuleManifest:
...,
] = ()
architecture: ModuleArchitectureDeclaration | None = None
information_governance: ModuleInformationGovernance = field(
default_factory=ModuleInformationGovernance
)
external_providers: tuple[ExternalProviderDeclaration, ...] = ()
external_provider_state_providers: tuple[
ExternalProviderStateProviderRegistration,
+10
View File
@@ -32,6 +32,9 @@ from govoplan_core.core.module_entitlements import (
current_tenant_execution_context,
tenant_execution_scope,
)
from govoplan_core.core.information_governance import (
information_governance_maturity_issues,
)
from govoplan_core.core.ownership import (
OwnershipProviderRegistration,
ResourceOwnershipProvider,
@@ -788,6 +791,13 @@ def _validate_architecture_declarations(manifest: ModuleManifest) -> None:
raise RegistryError(
f"Module {manifest.id!r} architecture declaration: {issue}"
)
for issue in information_governance_maturity_issues(
manifest.information_governance,
maturity=architecture.maturity if architecture is not None else None,
):
raise RegistryError(
f"Module {manifest.id!r} information-governance declaration: {issue}"
)
provider_ids: set[str] = set()
declared_capabilities = {
+1 -1
View File
@@ -73,7 +73,7 @@ def bootstrap_dev_data(
) -> BootstrapResult:
tenant = session.query(Tenant).filter(Tenant.slug == tenant_slug).one_or_none()
if tenant is None:
tenant = Tenant(slug=tenant_slug, name="Default Tenant", default_locale="en", settings={})
tenant = Tenant(slug=tenant_slug, name="Default Tenant", default_locale="de", settings={})
session.add(tenant)
session.flush()
+4 -1
View File
@@ -685,7 +685,10 @@ def configured_migration_heads(
manifest_factories=manifest_factories,
migration_track=migration_track,
)
return tuple(sorted(ScriptDirectory.from_config(config).get_heads()))
scripts = ScriptDirectory.from_config(config)
return tuple(
sorted(revision.revision for revision in scripts.get_revisions("heads"))
)
def database_is_at_configured_heads(
+14 -7
View File
@@ -4,10 +4,12 @@ import re
from typing import Any, Iterable
I18N_SETTINGS_KEY = "i18n"
REFERENCE_LANGUAGE_CODE = "de"
SOURCE_LANGUAGE_CODE = "en"
DEFAULT_LANGUAGE_PACKAGES: tuple[dict[str, str], ...] = (
{"code": "en", "label": "English", "native_label": "English"},
{"code": "de", "label": "German", "native_label": "Deutsch"},
{"code": "en", "label": "English", "native_label": "English"},
)
@@ -66,7 +68,7 @@ def normalize_enabled_language_codes(
available_languages: Iterable[dict[str, Any]],
*,
default_locale: object = None,
fallback_codes: Iterable[str] = ("en", "de"),
fallback_codes: Iterable[str] = (REFERENCE_LANGUAGE_CODE, SOURCE_LANGUAGE_CODE),
) -> list[str]:
available_codes = [normalize_language_code(item.get("code")) for item in available_languages if isinstance(item, dict)]
available = {code for code in available_codes if code}
@@ -88,8 +90,10 @@ def normalize_enabled_language_codes(
if enabled:
return enabled
if "en" in available:
return ["en"]
if REFERENCE_LANGUAGE_CODE in available:
return [REFERENCE_LANGUAGE_CODE]
if SOURCE_LANGUAGE_CODE in available:
return [SOURCE_LANGUAGE_CODE]
return available_codes[:1]
@@ -126,7 +130,7 @@ def preferred_language_code(user_settings: dict[str, Any] | None, allowed_codes:
default = resolve_language_code(default_locale, enabled)
if default:
return default
return enabled[0] if enabled else "en"
return enabled[0] if enabled else REFERENCE_LANGUAGE_CODE
def update_i18n_settings(settings: dict[str, Any] | None, **values: Any) -> dict[str, Any]:
@@ -141,10 +145,13 @@ def system_i18n_payload(settings_item: Any | None) -> dict[str, object]:
settings = getattr(settings_item, "settings", None)
packages = system_language_packages(settings)
available_codes = [item["code"] for item in packages]
default_language = resolve_language_code(getattr(settings_item, "default_locale", None), available_codes) or "en"
default_language = (
resolve_language_code(getattr(settings_item, "default_locale", None), available_codes)
or REFERENCE_LANGUAGE_CODE
)
enabled = system_enabled_language_codes(settings, default_locale=default_language)
if default_language not in enabled:
default_language = enabled[0] if enabled else "en"
default_language = enabled[0] if enabled else REFERENCE_LANGUAGE_CODE
return {
"available_languages": packages,
"enabled_languages": enabled,
+10 -2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from contextlib import asynccontextmanager
from importlib.metadata import PackageNotFoundError, version
from fastapi import Depends, FastAPI
from fastapi import HTTPException, status
@@ -20,6 +21,13 @@ from govoplan_core.core.runtime_coordination import (
from govoplan_core.settings import Settings, settings
def _core_distribution_version() -> str:
try:
return version("govoplan-core")
except PackageNotFoundError:
return "development"
def _dev_bootstrap_needs_create_all(database_url: str) -> bool:
try:
return make_url(database_url).get_backend_name() == "sqlite"
@@ -61,7 +69,7 @@ async def lifespan(app: FastAPI):
)
runtime_agent = RuntimeNodeAgent(
settings=settings,
software_version=app.version,
software_version=_core_distribution_version(),
module_ids=module_ids,
metadata={"process": "api"},
identity=configured_identity
@@ -93,7 +101,7 @@ def register_health_details(
):
app.state.govoplan_runtime_identity = runtime_identity(
active_settings,
software_version=app.version,
software_version=_core_distribution_version(),
module_ids=module_ids,
)
bind_process_runtime_identity(app.state.govoplan_runtime_identity)
+43 -7
View File
@@ -142,14 +142,10 @@ def _frontend_view_surfaces(manifest: ModuleManifest) -> list[dict[str, object]]
def _documentation_help_contexts(manifest: ModuleManifest) -> list[dict[str, object]]:
contexts: list[dict[str, object]] = []
seen: set[str] = set()
for topic in manifest.documentation:
raw_contexts = topic.metadata.get("help_contexts", ())
if isinstance(raw_contexts, str) or not isinstance(raw_contexts, (list, tuple, set)):
continue
for raw_context in raw_contexts:
context_id = str(raw_context).strip()
def append_context(context_id: str, topic) -> None:
if not context_id or context_id in seen:
continue
return
seen.add(context_id)
contexts.append(
{
@@ -159,6 +155,45 @@ def _documentation_help_contexts(manifest: ModuleManifest) -> list[dict[str, obj
"documentation_types": list(topic.documentation_types),
}
)
for topic in manifest.documentation:
raw_contexts = topic.metadata.get("help_contexts", ())
if isinstance(raw_contexts, str) or not isinstance(raw_contexts, (list, tuple, set)):
continue
for raw_context in raw_contexts:
context_id = str(raw_context).strip()
append_context(context_id, topic)
frontend = manifest.frontend
if frontend is None or not manifest.documentation:
return contexts
ordered_topics = sorted(manifest.documentation, key=lambda item: (item.order, item.id))
def baseline_topic(documentation_type: str):
return next(
(
topic
for topic in ordered_topics
if documentation_type in topic.documentation_types
),
ordered_topics[0],
)
user_topic = baseline_topic("user")
admin_topic = baseline_topic("admin")
for route in frontend.routes:
if route.surface_id:
append_context(route.surface_id, user_topic)
for route in frontend.settings_routes:
if route.surface_id:
append_context(route.surface_id, admin_topic)
for item in frontend.nav_items:
if item.surface_id:
append_context(item.surface_id, user_topic)
for surface in frontend.view_surfaces:
topic = admin_topic if ".admin." in surface.id else user_topic
append_context(surface.id, topic)
return contexts
@@ -253,6 +288,7 @@ def create_platform_router(settings: object | None = None) -> APIRouter:
if manifest.architecture is not None
else None
),
"information_governance": manifest.information_governance.to_dict(),
"external_providers": [
declaration.to_dict()
for declaration in manifest.external_providers
+1 -1
View File
@@ -33,7 +33,7 @@ class Tenant:
slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str | None] = mapped_column(Text)
default_locale: Mapped[str] = mapped_column(String(20), default="en", nullable=False)
default_locale: Mapped[str] = mapped_column(String(20), default="de", nullable=False)
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
allow_custom_groups: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
allow_custom_roles: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
+43
View File
@@ -184,6 +184,12 @@ class DatabaseMigrationTests(unittest.TestCase):
system_settings_count = connection.execute(
text("SELECT COUNT(*) FROM core_system_settings WHERE id = 'global'"),
).scalar_one()
default_locale = connection.execute(
text(
"SELECT default_locale FROM core_system_settings "
"WHERE id = 'global'"
),
).scalar_one()
current = database_migration_heads(connection)
self.assertEqual(
@@ -204,6 +210,7 @@ class DatabaseMigrationTests(unittest.TestCase):
self.assertIn("file_assets", tables)
self.assertIn("mail_server_profiles", tables)
self.assertEqual(system_settings_count, 1)
self.assertEqual(default_locale, "de")
self.assertEqual(
role_flags,
{"system_owner": True, "system_admin": False, "system_auditor": False},
@@ -211,6 +218,42 @@ class DatabaseMigrationTests(unittest.TestCase):
finally:
engine.dispose()
def test_german_reference_migration_preserves_explicit_english(self) -> None:
with tempfile.TemporaryDirectory(prefix="govoplan-locale-migration-test-") as directory:
database = Path(directory) / "locale.db"
url = f"sqlite:///{database}"
config = alembic_config(database_url=url, enabled_modules=())
command.upgrade(config, "f25c9d3e7a01")
engine = create_engine(url)
try:
with engine.begin() as connection:
connection.execute(
text(
"UPDATE core_system_settings "
"SET default_locale = 'en', "
"updated_at = '2040-01-01 00:00:00' "
"WHERE id = 'global'"
)
)
finally:
engine.dispose()
command.upgrade(config, "heads")
engine = create_engine(url)
try:
with engine.connect() as connection:
default_locale = connection.execute(
text(
"SELECT default_locale FROM core_system_settings "
"WHERE id = 'global'"
)
).scalar_one()
self.assertEqual(default_locale, "en")
finally:
engine.dispose()
def test_default_module_baselines_apply_to_fresh_database(self) -> None:
with tempfile.TemporaryDirectory(prefix="govoplan-default-baseline-test-") as directory:
database = Path(directory) / "default.db"
+29
View File
@@ -0,0 +1,29 @@
from __future__ import annotations
import unittest
from govoplan_core.i18n import (
REFERENCE_LANGUAGE_CODE,
normalize_enabled_language_codes,
normalize_language_packages,
preferred_language_code,
system_i18n_payload,
)
class I18nReferenceLanguageTests(unittest.TestCase):
def test_german_is_the_new_installation_reference_default(self) -> None:
packages = normalize_language_packages()
self.assertEqual("de", REFERENCE_LANGUAGE_CODE)
self.assertEqual(["de", "en"], [item["code"] for item in packages])
self.assertEqual(
["de", "en"],
normalize_enabled_language_codes(None, packages),
)
self.assertEqual("de", system_i18n_payload(None)["default_language"])
self.assertEqual("de", preferred_language_code(None, ()))
if __name__ == "__main__":
unittest.main()
+68
View File
@@ -0,0 +1,68 @@
from __future__ import annotations
import unittest
from govoplan_core.core.information_governance import (
InformationGovernanceDeclarationError,
InformationGovernanceDimension,
ModuleInformationGovernance,
information_governance_from_mapping,
information_governance_maturity_issues,
)
class InformationGovernanceTests(unittest.TestCase):
def test_default_applies_contract_without_claiming_enforcement(self) -> None:
declaration = ModuleInformationGovernance()
self.assertTrue(declaration.current_authorization_for_historical_reads)
self.assertEqual(
{"contract_only"},
{item.adoption for item in declaration.dimensions.values()},
)
self.assertEqual("1", declaration.to_dict()["contract_version"])
def test_enforced_adoption_requires_evidence(self) -> None:
with self.assertRaisesRegex(
InformationGovernanceDeclarationError,
"requires evidence",
):
InformationGovernanceDimension(adoption="enforced")
with self.assertRaisesRegex(
InformationGovernanceDeclarationError,
"covered object types",
):
InformationGovernanceDimension(
adoption="enforced",
evidence=("tests/test_information_governance.py",),
)
def test_catalog_shape_round_trips(self) -> None:
declaration = ModuleInformationGovernance()
self.assertEqual(
declaration,
information_governance_from_mapping(declaration.to_dict()),
)
def test_reference_ready_rejects_unproved_dimensions(self) -> None:
issues = information_governance_maturity_issues(
ModuleInformationGovernance(),
maturity="reference_ready",
)
self.assertEqual(1, len(issues))
self.assertIn("temporal_browsing", issues[0])
self.assertIn("purpose_aware_access", issues[0])
self.assertEqual(
(),
information_governance_maturity_issues(
ModuleInformationGovernance(),
maturity="vertical_slice",
),
)
if __name__ == "__main__":
unittest.main()
+74 -1
View File
@@ -106,6 +106,7 @@ from govoplan_core.core.module_package_catalog import (
sign_module_package_catalog,
validate_module_package_catalog,
)
from govoplan_core.core.information_governance import ModuleInformationGovernance
from govoplan_core.core.modules import DocumentationTopic, FrontendModule, FrontendRoute, MigrationRetirementPlan, ModuleCompatibility, ModuleMigrationTask, ModuleMigrationTaskContext, ModuleMigrationTaskResult, ModuleUninstallGuardResult, PublicFrontendRoute
from govoplan_core.core.module_guards import drop_table_retirement_provider
from govoplan_core.core.modules import MigrationSpec, ModuleInterfaceProvider, ModuleInterfaceRequirement, ModuleManifest, PermissionDefinition, RoleTemplate
@@ -119,7 +120,8 @@ from govoplan_core.security.module_permissions import scopes_grant_compatible
from govoplan_core.security.permissions import scope_grants
from govoplan_core.server.app import create_app
from govoplan_core.server.config import GovoplanServerConfig
from govoplan_core.server.platform import create_platform_router
from govoplan_core.server.platform import _documentation_help_contexts, create_platform_router
from govoplan_core.core.views import ViewSurface
from govoplan_core.server.registry import available_module_manifests, build_platform_registry
from govoplan_core.server.route_validation import RouteCollisionError
from govoplan_core.tenancy.scope import Tenant, create_scope_tables
@@ -597,6 +599,70 @@ class ModuleSystemTests(unittest.TestCase):
[item["path"] for item in public_module["frontend"]["public_routes"]],
)
def test_declared_surfaces_receive_static_documentation_fallbacks(self) -> None:
manifest = ModuleManifest(
id="example",
name="Example",
version="test",
documentation=(
DocumentationTopic(
id="example.user",
title="Example user guide",
summary="User guide.",
documentation_types=("user",),
order=20,
),
DocumentationTopic(
id="example.admin",
title="Example administrator guide",
summary="Administrator guide.",
documentation_types=("admin",),
order=10,
),
),
frontend=FrontendModule(
module_id="example",
routes=(
FrontendRoute(
path="/example",
component="ExamplePage",
surface_id="example.workspace",
),
),
settings_routes=(
FrontendRoute(
path="/admin?section=example",
component="ExampleAdmin",
surface_id="example.admin.settings",
),
),
view_surfaces=(
ViewSurface(
id="example.workspace",
module_id="example",
kind="route",
label="Example workspace",
),
ViewSurface(
id="example.admin.settings",
module_id="example",
kind="section",
label="Example settings",
),
),
),
)
contexts = {
item["id"]: item for item in _documentation_help_contexts(manifest)
}
self.assertEqual("example.user", contexts["example.workspace"]["topic_id"])
self.assertEqual(
"example.admin",
contexts["example.admin.settings"]["topic_id"],
)
def test_registry_rejects_duplicate_public_frontend_routes(self) -> None:
registry = PlatformRegistry()
for module_id in ("first", "second"):
@@ -3300,6 +3366,7 @@ finally:
"recovery_notes": "Restore rehearsal completed for the release candidate.",
"provides_interfaces": [{"name": "files.spaces", "version": "1.2.0"}],
"requires_interfaces": [{"name": "access.directory", "version_min": "1.0.0", "optional": True}],
"information_governance": ModuleInformationGovernance().to_dict(),
"artifact_integrity": {
"python": {
"ref": "govoplan-files==0.1.4",
@@ -3345,6 +3412,12 @@ finally:
[{"name": "access.directory", "optional": True, "version_min": "1.0.0"}],
catalog[0]["requires_interfaces"],
)
self.assertEqual(
"contract_only",
catalog[0]["information_governance"]["dimensions"]["retention"][
"adoption"
],
)
self.assertEqual("0" * 64, catalog[0]["artifact_integrity"]["python"]["sha256"])
validation = validate_module_package_catalog(catalog_path)
+16
View File
@@ -40,6 +40,22 @@ class _Consumer:
self.added.append(queue)
def test_api_runtime_identity_uses_the_installed_distribution_version(
monkeypatch,
) -> None:
from govoplan_core.server import default_config
monkeypatch.setattr(
default_config,
"version",
lambda distribution: "0.1.15"
if distribution == "govoplan-core"
else "unexpected",
)
assert default_config._core_distribution_version() == "0.1.15"
def test_api_runtime_agent_fails_readiness_on_heartbeat_error() -> None:
agent = RuntimeNodeAgent(
settings=SimpleNamespace(
+23 -1
View File
@@ -1,12 +1,34 @@
from __future__ import annotations
import unittest
from unittest.mock import patch
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from govoplan_core.commands.wait_for_database import main
from govoplan_core.db.migrations import configured_migration_heads
class WaitForDatabaseTests(unittest.TestCase):
def test_configured_heads_resolve_cross_branch_dependencies(self) -> None:
scripts = MagicMock()
scripts.get_revisions.return_value = (
SimpleNamespace(revision="head-b"),
SimpleNamespace(revision="head-a"),
)
with (
patch("govoplan_core.db.migrations.alembic_config"),
patch(
"govoplan_core.db.migrations.ScriptDirectory.from_config",
return_value=scripts,
),
):
heads = configured_migration_heads("sqlite://")
self.assertEqual(("head-a", "head-b"), heads)
scripts.get_revisions.assert_called_once_with("heads")
scripts.get_heads.assert_not_called()
def test_waits_until_exact_configured_heads_are_visible(self) -> None:
with (
patch(
+76 -76
View File
@@ -1,12 +1,12 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.15",
"version": "0.1.16",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govoplan/core-webui",
"version": "0.1.15",
"version": "0.1.16",
"dependencies": {
"@govoplan/access-webui": "file:../../govoplan-access/webui",
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
@@ -73,12 +73,12 @@
},
"../../govoplan-access/webui": {
"name": "@govoplan/access-webui",
"version": "0.1.15",
"version": "0.1.16",
"devDependencies": {
"typescript": "^5.7.2"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -92,9 +92,9 @@
},
"../../govoplan-addresses/webui": {
"name": "@govoplan/addresses-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -108,12 +108,12 @@
},
"../../govoplan-admin/webui": {
"name": "@govoplan/admin-webui",
"version": "0.1.15",
"version": "0.1.16",
"devDependencies": {
"typescript": "^5.7.2"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -127,9 +127,9 @@
},
"../../govoplan-approvals/webui": {
"name": "@govoplan/approvals-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
@@ -142,9 +142,9 @@
},
"../../govoplan-audit/webui": {
"name": "@govoplan/audit-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -158,9 +158,9 @@
},
"../../govoplan-calendar/webui": {
"name": "@govoplan/calendar-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -177,7 +177,7 @@
},
"../../govoplan-campaign/webui": {
"name": "@govoplan/campaign-webui",
"version": "0.1.15",
"version": "0.1.16",
"dependencies": {
"read-excel-file": "9.2.0"
},
@@ -185,7 +185,7 @@
"typescript": "^5.7.2"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -199,9 +199,9 @@
},
"../../govoplan-cases/webui": {
"name": "@govoplan/cases-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -215,9 +215,9 @@
},
"../../govoplan-committee/webui": {
"name": "@govoplan/committee-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -231,9 +231,9 @@
},
"../../govoplan-dashboard/webui": {
"name": "@govoplan/dashboard-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -247,9 +247,9 @@
},
"../../govoplan-dataflow/webui": {
"name": "@govoplan/dataflow-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@xyflow/react": "^12.11.2",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -265,9 +265,9 @@
},
"../../govoplan-datasources/webui": {
"name": "@govoplan/datasources-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -282,9 +282,9 @@
},
"../../govoplan-dist-lists/webui": {
"name": "@govoplan/dist-lists-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -299,9 +299,9 @@
},
"../../govoplan-docs/webui": {
"name": "@govoplan/docs-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -318,9 +318,9 @@
},
"../../govoplan-encryption/webui": {
"name": "@govoplan/encryption-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
@@ -333,9 +333,9 @@
},
"../../govoplan-files/webui": {
"name": "@govoplan/files-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -352,9 +352,9 @@
},
"../../govoplan-forms-runtime/webui": {
"name": "@govoplan/forms-runtime-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -368,9 +368,9 @@
},
"../../govoplan-forms/webui": {
"name": "@govoplan/forms-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
@@ -383,9 +383,9 @@
},
"../../govoplan-identity-trust/webui": {
"name": "@govoplan/identity-trust-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
@@ -398,9 +398,9 @@
},
"../../govoplan-idm/webui": {
"name": "@govoplan/idm-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -417,12 +417,12 @@
},
"../../govoplan-mail/webui": {
"name": "@govoplan/mail-webui",
"version": "0.1.15",
"version": "0.1.16",
"devDependencies": {
"typescript": "^5.7.2"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -436,9 +436,9 @@
},
"../../govoplan-notifications/webui": {
"name": "@govoplan/notifications-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -455,9 +455,9 @@
},
"../../govoplan-ops/webui": {
"name": "@govoplan/ops-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -474,9 +474,9 @@
},
"../../govoplan-organizations/webui": {
"name": "@govoplan/organizations-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -493,9 +493,9 @@
},
"../../govoplan-policy/webui": {
"name": "@govoplan/policy-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -509,9 +509,9 @@
},
"../../govoplan-portal/webui": {
"name": "@govoplan/portal-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -525,9 +525,9 @@
},
"../../govoplan-postbox/webui": {
"name": "@govoplan/postbox-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -541,9 +541,9 @@
},
"../../govoplan-projects/webui": {
"name": "@govoplan/projects-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -557,9 +557,9 @@
},
"../../govoplan-reporting/webui": {
"name": "@govoplan/reporting-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -573,9 +573,9 @@
},
"../../govoplan-risk-compliance/webui": {
"name": "@govoplan/risk-compliance-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -589,9 +589,9 @@
},
"../../govoplan-scheduling/webui": {
"name": "@govoplan/scheduling-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -608,9 +608,9 @@
},
"../../govoplan-search/webui": {
"name": "@govoplan/search-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -624,9 +624,9 @@
},
"../../govoplan-templates/webui": {
"name": "@govoplan/templates-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -641,9 +641,9 @@
},
"../../govoplan-tenancy/webui": {
"name": "@govoplan/tenancy-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
@@ -656,9 +656,9 @@
},
"../../govoplan-views/webui": {
"name": "@govoplan/views-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -672,9 +672,9 @@
},
"../../govoplan-voting/webui": {
"name": "@govoplan/voting-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
@@ -687,9 +687,9 @@
},
"../../govoplan-workflow/webui": {
"name": "@govoplan/workflow-webui",
"version": "0.1.15",
"version": "0.1.16",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@xyflow/react": "^12.11.2",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
+60 -60
View File
@@ -1,26 +1,26 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.15",
"version": "0.1.16",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govoplan/core-webui",
"version": "0.1.15",
"version": "0.1.16",
"dependencies": {
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.15",
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.15",
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.15",
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.15",
"@govoplan/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.15",
"@govoplan/dashboard-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#v0.1.15",
"@govoplan/docs-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#v0.1.15",
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.15",
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.15",
"@govoplan/mail-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#v0.1.15",
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.15",
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.15",
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.15",
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.16",
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.16",
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.16",
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.16",
"@govoplan/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.16",
"@govoplan/dashboard-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#v0.1.16",
"@govoplan/docs-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#v0.1.16",
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.16",
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.16",
"@govoplan/mail-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#v0.1.16",
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.16",
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.16",
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.16",
"@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2",
"@tiptap/pm": "^3.29.2",
@@ -753,10 +753,10 @@
"optional": true
},
"node_modules/@govoplan/access-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#794fcf90f8d759eb140d3b5b0a880482912a79be",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#a7ed28f6a3cf9e3f5f549db9b0e0785910a8c41a",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -769,10 +769,10 @@
}
},
"node_modules/@govoplan/admin-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#57e2a34c894e20afa46558b83d167e36a1db26c0",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#3842987a4ed2b550a3e3c6932db52fa09995e5b4",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -785,10 +785,10 @@
}
},
"node_modules/@govoplan/audit-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#6f095eb56293fa4bc3f3ca5a52d36491c1713b00",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#4613b54c06863689eade1012632bd2398d15fef1",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -801,10 +801,10 @@
}
},
"node_modules/@govoplan/calendar-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#f6faaf196da1891997c5ad7065bbba69aacd018d",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#128c78597edbc6a6fcac844fe1c08b2f9917a872",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -820,13 +820,13 @@
}
},
"node_modules/@govoplan/campaign-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#ea0efe661f10474e0a8df0bffde75f3a78577f27",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#2db99eaf6a36b433093d37f53cef0125f4b98599",
"dependencies": {
"read-excel-file": "9.2.0"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -839,10 +839,10 @@
}
},
"node_modules/@govoplan/dashboard-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#c2c7ba8fb2090ea0e6abcb2f3cd46916a9016b1e",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#77924e7a2c8366be39c9305477e085802a578d0b",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -855,10 +855,10 @@
}
},
"node_modules/@govoplan/docs-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#1f5050530589da651a49e8d617bf0825e6332388",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#8cbab781bf9126062efc6127c89c7eed308955de",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -874,10 +874,10 @@
}
},
"node_modules/@govoplan/files-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#0293e49defd34a958584c9665ba006d22db497e1",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#edee29af51fba34c569defc98a977a0ed80c5718",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -893,10 +893,10 @@
}
},
"node_modules/@govoplan/idm-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#bd72a3f277ce4f214906e276ee1553f4fd23364d",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#6e9393c9c7606e91ced908dceef69fd18cf2abe7",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -912,10 +912,10 @@
}
},
"node_modules/@govoplan/mail-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#622f63b4b5fd427128b1a6a9df0ecbe357059d69",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#a040de4fe5d9af4a5f434a87d2c5fa8b79e6a818",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -928,10 +928,10 @@
}
},
"node_modules/@govoplan/ops-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#ce074044056827f253eaa9bae920136ebdd8ada8",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#02e5d91d565bf7d42cc1b550f18e73621a93a6fa",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -947,10 +947,10 @@
}
},
"node_modules/@govoplan/organizations-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#aab7f25e866b9738390e2d3026e784d3ad9c3a54",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#18e4df348a3cf69602b2d2491e6ae28c26551b59",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"@vitejs/plugin-react": "^5.2.0",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
@@ -966,10 +966,10 @@
}
},
"node_modules/@govoplan/policy-webui": {
"version": "0.1.15",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#44d72914aea27a74511fd50f445c450217e3cd5f",
"version": "0.1.16",
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#107cc3b654b7b97ec441650bcc3bac6a7accc8a0",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.15",
"@govoplan/core-webui": "^0.1.16",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -2059,9 +2059,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.400",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz",
"integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==",
"version": "1.5.401",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.401.tgz",
"integrity": "sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==",
"license": "ISC"
},
"node_modules/esbuild": {
@@ -2330,9 +2330,9 @@
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz",
"integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.15",
"version": "0.1.16",
"private": true,
"type": "module",
"main": "src/index.ts",
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.15",
"version": "0.1.16",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -26,19 +26,19 @@
"preview": "vite preview --host 127.0.0.1 --port 4173"
},
"dependencies": {
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.15",
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.15",
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.15",
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.15",
"@govoplan/dashboard-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#v0.1.15",
"@govoplan/docs-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#v0.1.15",
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.15",
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.15",
"@govoplan/mail-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#v0.1.15",
"@govoplan/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.15",
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.15",
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.15",
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.15",
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.16",
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.16",
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.16",
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.16",
"@govoplan/dashboard-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#v0.1.16",
"@govoplan/docs-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#v0.1.16",
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.16",
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.16",
"@govoplan/mail-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#v0.1.16",
"@govoplan/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.16",
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.16",
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.16",
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.16",
"@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2",
"@tiptap/pm": "^3.29.2",
+1 -1
View File
@@ -644,7 +644,7 @@ function normalizeAuthInfo(response: AuthPayload): AuthInfo {
principal,
available_languages: response.available_languages,
enabled_language_codes: profileLoaded ? response.enabled_language_codes ?? activeTenant.enabled_language_codes ?? [] : undefined,
default_language: profileLoaded ? response.default_language ?? user.preferred_language ?? activeTenant.default_locale ?? "en" : undefined,
default_language: profileLoaded ? response.default_language ?? user.preferred_language ?? activeTenant.default_locale ?? "de" : undefined,
profile_loaded: profileLoaded,
roles_loaded: rolesLoaded,
groups_loaded: groupsLoaded
+9 -4
View File
@@ -32,14 +32,15 @@ type PlatformLanguageProviderProps = {
const STORAGE_KEY = "govoplan.language";
const PRIMARY_LANGUAGE = "en";
const REFERENCE_LANGUAGE = "de";
const I18N_MESSAGE_SEPARATOR = "::";
const TRANSLATABLE_ATTRIBUTES = ["aria-label", "alt", "placeholder", "title"];
const SKIP_TRANSLATION_SELECTOR = "script, style, code, pre, textarea, [data-i18n-skip]";
const textNodeOriginals = new WeakMap<Text, string>();
export const DEFAULT_AVAILABLE_LANGUAGES: PlatformLanguage[] = [
{ code: "en", label: "i18n:govoplan-core.english.649df08a", nativeLabel: "i18n:govoplan-core.language_native_english" },
{ code: "de", label: "i18n:govoplan-core.german.da91388c", nativeLabel: "i18n:govoplan-core.language_native_german" }];
{ code: "de", label: "i18n:govoplan-core.german.da91388c", nativeLabel: "i18n:govoplan-core.language_native_german" },
{ code: "en", label: "i18n:govoplan-core.english.649df08a", nativeLabel: "i18n:govoplan-core.language_native_english" }];
export const DEFAULT_TRANSLATIONS: PlatformTranslations = {
@@ -186,13 +187,17 @@ function languagesForCodes(languages: PlatformLanguage[], codes?: string[] | nul
function preferredLanguage(languages: PlatformLanguage[], preferred?: string | null): string {
const codes = new Set(languages.map((item) => item.code));
const requested = normalizeLanguageCode(preferred ?? storedLanguage() ?? browserLanguage());
const requestedValue = preferred ?? storedLanguage() ?? browserLanguage();
if (requestedValue) {
const requested = normalizeLanguageCode(requestedValue);
if (codes.has(requested)) return requested;
const primary = primaryLanguageCode(requested);
const primaryMatch = languages.find((item) => primaryLanguageCode(item.code) === primary);
if (primaryMatch) return primaryMatch.code;
}
if (codes.has(REFERENCE_LANGUAGE)) return REFERENCE_LANGUAGE;
if (codes.has(PRIMARY_LANGUAGE)) return PRIMARY_LANGUAGE;
return languages[0]?.code ?? PRIMARY_LANGUAGE;
return languages[0]?.code ?? REFERENCE_LANGUAGE;
}
function primaryLanguageCode(value: string): string {
+10
View File
@@ -1124,6 +1124,16 @@ export type PlatformModuleInfo = {
target_tested_providers: string[];
documentation: Record<string, string[]>;
} | null;
information_governance?: {
contract_version: string;
current_authorization_for_historical_reads: boolean;
dimensions: Record<string, {
adoption: "not_applicable" | "contract_only" | "partial" | "enforced";
object_types: string[];
evidence: string[];
limitation?: string | null;
}>;
};
external_providers?: Array<{
contract_version: string;
id: string;