Compare commits

...
4 Commits
Author SHA1 Message Date
zemion fa2d5d40dd Release Core v0.1.33 with redirect-sensitive headers
Module Package Release / publish-packages (push) Successful in 13s
2026-08-22 16:11:02 +02:00
zemion 6ccef162f6 Release Core v0.1.32 with bounded HTTP request bodies
Module Package Release / publish-packages (push) Successful in 14s
2026-08-22 14:32:24 +02:00
zemion 48dac139a5 feat(wiki): compose governed Wiki WebUI
Module Package Release / publish-packages (push) Successful in 12s
2026-08-22 13:32:20 +02:00
zemion a090e5af20 feat(tickets): add optional integration contracts and WebUI composition
Module Package Release / publish-packages (push) Successful in 13s
2026-08-22 12:06:04 +02:00
13 changed files with 1048 additions and 437 deletions
+1
View File
@@ -18,6 +18,7 @@ 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. |
| Provider-neutral record filing | `RECORDS_FILING_CONTRACT.md` | Exact source-revision identity, current source authorization, idempotent filing, capability discovery, and ownership boundary. |
| Ticket routing and Case escalation | `TICKET_INTEGRATION_CONTRACTS.md` | Optional fail-open routing, replay-safe Case handoff, authorization, evidence, and ownership boundaries. |
| 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. |
| Data-subject access and erasure requests | `DATA_SUBJECT_REQUESTS.md` | Provider-owned search and mutation, explicit coverage, governed export, retained evidence, permissions, and idempotent execution. |
+59
View File
@@ -0,0 +1,59 @@
# Ticket Integration Capability Contracts
Core owns two narrow, optional contracts that let the Tickets module compose
with policy and formal-procedure modules without importing either one. Tickets
remains the authority for operational ticket identity, lifecycle, assignment,
comments, links, and immutable history.
## Capability Names
- `tickets.routing` optionally supplies a `TicketRoutingProvider`.
- `tickets.case_escalation` optionally supplies a
`TicketCaseEscalationProvider`.
Both contracts are version 1 and are defined in
`govoplan_core.core.tickets`. Registry helpers return `None` when a capability
is absent or has the wrong shape, so optional-module absence is normal runtime
state rather than a startup failure.
## Routing
Tickets sends a bounded, tenant-scoped `TicketRoutingRequest` containing the
ticket reference, type, priority, title, receive time, optional queue hint, and
non-secret attributes. The provider returns its identity and may return a queue
reference, timezone-aware service target, human-readable explanation, and
bounded metadata.
The provider is advisory. Tickets snapshots any returned queue and target into
its own record and history. An absent provider, a no-match plan, or an absent
queue must not prevent ticket intake; authorized staff can route manually.
Providers must not persist a second ticket lifecycle.
## Case Escalation
Tickets sends a `TicketCaseEscalationCommand` with stable tenant, ticket, and
display references, the requested Case type, actor-visible handoff note,
timezone-aware occurrence time, and an idempotency key. The provider returns a
stable Case identifier, number, bounded application-relative URL, replay flag,
and bounded metadata.
Providers must:
- recheck tenant and Case-creation authorization;
- reject an absent or inactive requested Case type;
- make identical retries resolve the same Case;
- preserve the Ticket reference in governed Case context; and
- return only an application-relative path, never an untrusted external URL.
Tickets records the result and its own escalation evidence. Cases remains the
authority for the formal procedure; Tickets remains the authority for the
operational request. Creating a Case does not merge or silently close either
lifecycle.
## Failure And Transaction Semantics
Capability calls receive the caller's active persistence session so a concrete
provider can participate in the same unit of work. Authorization and validation
errors fail the requested routing/escalation mutation explicitly. The caller
must still apply its own permission checks, tenant boundary, replay protection,
and immutable evidence rules.
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-core"
version = "0.1.29"
version = "0.1.33"
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
readme = "README.md"
requires-python = ">=3.12"
+179
View File
@@ -0,0 +1,179 @@
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Mapping, Protocol, runtime_checkable
TICKET_INTEGRATION_CONTRACT_VERSION = "1"
CAPABILITY_TICKET_ROUTING = "tickets.routing"
CAPABILITY_TICKET_CASE_ESCALATION = "tickets.case_escalation"
@dataclass(frozen=True, slots=True)
class TicketRoutingRequest:
tenant_id: str
ticket_id: str
ticket_type: str
priority: str
title: str
received_at: datetime
queue_hint: str | None = None
attributes: Mapping[str, object] = field(default_factory=dict)
def __post_init__(self) -> None:
_required(self.tenant_id, "Ticket routing tenant", 255)
_required(self.ticket_id, "Ticket routing ticket", 255)
_required(self.ticket_type, "Ticket routing type", 80)
_required(self.priority, "Ticket routing priority", 40)
_required(self.title, "Ticket routing title", 500)
_aware(self.received_at, "Ticket routing received_at")
_optional(self.queue_hint, "Ticket routing queue hint", 255)
if len(self.attributes) > 100:
raise ValueError("Ticket routing attributes are limited to 100 entries.")
@dataclass(frozen=True, slots=True)
class TicketRoutingPlan:
provider_id: str
queue_ref: str | None = None
service_target_at: datetime | None = None
explanation: str | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
def __post_init__(self) -> None:
_required(self.provider_id, "Ticket routing provider", 200)
_optional(self.queue_ref, "Ticket routing queue reference", 255)
_optional(self.explanation, "Ticket routing explanation", 4_000)
_aware(self.service_target_at, "Ticket routing service_target_at")
if len(self.metadata) > 100:
raise ValueError("Ticket routing metadata is limited to 100 entries.")
@runtime_checkable
class TicketRoutingProvider(Protocol):
def route_ticket(
self,
session: object,
principal: object,
*,
request: TicketRoutingRequest,
) -> TicketRoutingPlan: ...
@dataclass(frozen=True, slots=True)
class TicketCaseEscalationCommand:
tenant_id: str
ticket_id: str
ticket_number: str
title: str
case_type_key: str
occurred_at: datetime
idempotency_key: str
handoff_note: str | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
def __post_init__(self) -> None:
_required(self.tenant_id, "Ticket escalation tenant", 255)
_required(self.ticket_id, "Ticket escalation ticket", 255)
_required(self.ticket_number, "Ticket escalation number", 255)
_required(self.title, "Ticket escalation title", 500)
_required(self.case_type_key, "Ticket escalation case type", 120)
_required(self.idempotency_key, "Ticket escalation idempotency key", 255)
_optional(self.handoff_note, "Ticket escalation handoff note", 10_000)
_aware(self.occurred_at, "Ticket escalation occurred_at")
if len(self.metadata) > 100:
raise ValueError("Ticket escalation metadata is limited to 100 entries.")
@dataclass(frozen=True, slots=True)
class TicketCaseEscalationResult:
provider_id: str
case_id: str
case_number: str
case_url: str
replayed: bool = False
metadata: Mapping[str, object] = field(default_factory=dict)
def __post_init__(self) -> None:
_required(self.provider_id, "Ticket escalation provider", 200)
_required(self.case_id, "Ticket escalation case", 255)
_required(self.case_number, "Ticket escalation case number", 255)
_relative_url(self.case_url)
if len(self.metadata) > 100:
raise ValueError("Ticket escalation metadata is limited to 100 entries.")
@runtime_checkable
class TicketCaseEscalationProvider(Protocol):
def escalate_ticket(
self,
session: object,
principal: object,
*,
command: TicketCaseEscalationCommand,
) -> TicketCaseEscalationResult: ...
def ticket_routing_provider(registry: object | None) -> TicketRoutingProvider | None:
provider = _capability(registry, CAPABILITY_TICKET_ROUTING)
return provider if isinstance(provider, TicketRoutingProvider) else None
def ticket_case_escalation_provider(
registry: object | None,
) -> TicketCaseEscalationProvider | None:
provider = _capability(registry, CAPABILITY_TICKET_CASE_ESCALATION)
return provider if isinstance(provider, TicketCaseEscalationProvider) else None
def _capability(registry: object | None, name: str) -> object | None:
if (
registry is None
or not hasattr(registry, "has_capability")
or not hasattr(registry, "capability")
or not registry.has_capability(name)
):
return None
return registry.capability(name)
def _required(value: str, label: str, maximum: int) -> None:
if not value.strip() or len(value) > maximum:
raise ValueError(f"{label} must contain 1 to {maximum} characters.")
def _optional(value: str | None, label: str, maximum: int) -> None:
if value is not None and (not value.strip() or len(value) > maximum):
raise ValueError(f"{label} must contain 1 to {maximum} characters when set.")
def _aware(value: datetime | None, label: str) -> None:
if value is not None and (value.tzinfo is None or value.utcoffset() is None):
raise ValueError(f"{label} must include a timezone.")
def _relative_url(value: str) -> None:
if (
not value.startswith("/")
or value.startswith("//")
or "\\" in value
or len(value) > 1_500
or any(ord(character) < 32 or ord(character) == 127 for character in value)
):
raise ValueError("Ticket escalation URLs must be bounded application-relative paths.")
__all__ = [
"CAPABILITY_TICKET_CASE_ESCALATION",
"CAPABILITY_TICKET_ROUTING",
"TICKET_INTEGRATION_CONTRACT_VERSION",
"TicketCaseEscalationCommand",
"TicketCaseEscalationProvider",
"TicketCaseEscalationResult",
"TicketRoutingPlan",
"TicketRoutingProvider",
"TicketRoutingRequest",
"ticket_case_escalation_provider",
"ticket_routing_provider",
]
+44 -5
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import urllib.parse
import urllib.request
from collections.abc import Iterable
from dataclasses import dataclass
from typing import Mapping
@@ -12,6 +13,12 @@ from govoplan_core.security.outbound_http import (
)
MAX_OUTBOUND_HTTP_REQUEST_BODY_BYTES = 1_000_000
_STANDARD_REDIRECT_SENSITIVE_HEADERS = frozenset(
{"authorization", "proxy-authorization", "cookie", "cookie2"}
)
@dataclass(frozen=True, slots=True)
class HttpFetchResponse:
status: int
@@ -46,15 +53,27 @@ def fetch_http(
label: str = "URL",
method: str = "GET",
headers: Mapping[str, str] | None = None,
body: bytes | None = None,
max_bytes: int | None = None,
redirect_sensitive_headers: Iterable[str] = (),
) -> HttpFetchResponse:
if body is not None and len(body) > MAX_OUTBOUND_HTTP_REQUEST_BODY_BYTES:
raise ValueError(
"Outbound HTTP request body exceeds the 1000000-byte safety limit."
)
validated_url = validate_outbound_http_url(url, label=label)
request = urllib.request.Request( # noqa: S310 - URL is restricted to validated HTTP(S).
validated_url,
data=body,
headers=dict(headers or {}),
method=method,
)
opener = build_outbound_http_opener(_PolicyRedirectHandler(label=label))
opener = build_outbound_http_opener(
_PolicyRedirectHandler(
label=label,
sensitive_headers=redirect_sensitive_headers,
)
)
with opener.open(request, timeout=timeout) as response: # noqa: S310 - URL and every redirect are policy-validated. # nosec B310 # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
response_headers = dict(response.headers.items())
return HttpFetchResponse(
@@ -76,16 +95,35 @@ def fetch_http_text(
label: str = "URL",
method: str = "GET",
headers: Mapping[str, str] | None = None,
body: bytes | None = None,
encoding: str = "utf-8",
max_bytes: int | None = None,
redirect_sensitive_headers: Iterable[str] = (),
) -> str:
return fetch_http(url, timeout=timeout, label=label, method=method, headers=headers, max_bytes=max_bytes).text(encoding)
return fetch_http(
url,
timeout=timeout,
label=label,
method=method,
headers=headers,
body=body,
max_bytes=max_bytes,
redirect_sensitive_headers=redirect_sensitive_headers,
).text(encoding)
class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
def __init__(self, *, label: str) -> None:
def __init__(
self,
*,
label: str,
sensitive_headers: Iterable[str] = (),
) -> None:
super().__init__()
self._label = label
self._sensitive_headers = _STANDARD_REDIRECT_SENSITIVE_HEADERS | {
value.strip().lower() for value in sensitive_headers if value.strip()
}
def redirect_request(self, req, fp, code, msg, headers, newurl): # type: ignore[no-untyped-def]
candidate = validate_outbound_http_url(newurl, label=f"{self._label} redirect")
@@ -95,8 +133,9 @@ class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
return None
new_request = super().redirect_request(req, fp, code, msg, headers, candidate)
if new_request is not None and _http_origin(previous) != _http_origin(redirected):
for header in ("Authorization", "Proxy-Authorization", "Cookie", "Cookie2"):
new_request.remove_header(header)
for header in tuple(new_request.headers) + tuple(new_request.unredirected_hdrs):
if header.lower() in self._sensitive_headers:
new_request.remove_header(header)
return new_request
+91 -4
View File
@@ -2,9 +2,14 @@ from __future__ import annotations
import io
import unittest
from unittest.mock import patch
from unittest.mock import Mock, patch
from govoplan_core.security.http_fetch import _PolicyRedirectHandler, is_http_url, validate_http_url
from govoplan_core.security.http_fetch import (
_PolicyRedirectHandler,
fetch_http,
is_http_url,
validate_http_url,
)
from govoplan_core.security.outbound_http import (
DEFAULT_FILE_TRANSFER_BYTES,
DEFAULT_STRUCTURED_RESPONSE_BYTES,
@@ -21,6 +26,51 @@ from govoplan_core.security.outbound_http import (
class HttpFetchTests(unittest.TestCase):
def test_fetch_http_forwards_a_bounded_request_body(self) -> None:
class Response(io.BytesIO):
status = 200
headers = {"Content-Type": "application/json"}
def __enter__(self):
return self
def __exit__(self, *_args):
return False
opener = Mock()
opener.open.return_value = Response(b"{}")
with patch(
"govoplan_core.security.http_fetch.validate_outbound_http_url",
return_value="https://wiki.example.test/api.php",
), patch(
"govoplan_core.security.http_fetch.build_outbound_http_opener",
return_value=opener,
):
response = fetch_http(
"https://wiki.example.test/api.php",
method="POST",
headers={"Content-Type": "application/x-www-form-urlencoded"},
body=b"action=edit",
max_bytes=1024,
)
request = opener.open.call_args.args[0]
self.assertEqual("POST", request.get_method())
self.assertEqual(b"action=edit", request.data)
self.assertEqual(b"{}", response.body)
def test_fetch_http_rejects_an_oversized_request_body_before_transport(self) -> None:
with patch(
"govoplan_core.security.http_fetch.validate_outbound_http_url"
) as validate:
with self.assertRaisesRegex(ValueError, "request body exceeds"):
fetch_http(
"https://wiki.example.test/api.php",
method="POST",
body=b"x" * 1_000_001,
)
validate.assert_not_called()
def test_validate_http_url_accepts_absolute_http_urls_without_credentials(self) -> None:
self.assertEqual("https://example.test/catalog.json", validate_http_url("https://example.test/catalog.json"))
self.assertTrue(is_http_url("http://example.test/catalog.json"))
@@ -189,9 +239,17 @@ class HttpFetchTests(unittest.TestCase):
request = urllib.request.Request(
"https://catalog.example.test/releases",
headers={"Authorization": "Bearer secret", "X-Request-ID": "request-1"},
headers={
"Authorization": "Bearer secret",
"Cookie": "session=secret",
"X-OTRS-Header-Password": "secret",
"X-Request-ID": "request-1",
},
)
handler = _PolicyRedirectHandler(
label="Catalog URL",
sensitive_headers=("X-OTRS-Header-Password",),
)
handler = _PolicyRedirectHandler(label="Catalog URL")
with patch.dict("os.environ", {"APP_ENV": "test"}), patch(
"govoplan_core.security.outbound_http.socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("127.0.0.1", 443))],
@@ -215,9 +273,38 @@ class HttpFetchTests(unittest.TestCase):
self.assertIsNotNone(redirected)
self.assertIsNone(redirected.get_header("Authorization"))
self.assertIsNone(redirected.get_header("Cookie"))
self.assertIsNone(redirected.get_header("X-otrs-header-password"))
self.assertEqual("request-1", redirected.get_header("X-request-id"))
self.assertIsNone(downgrade)
def test_core_redirects_preserve_caller_sensitive_headers_on_the_same_origin(self) -> None:
import urllib.request
request = urllib.request.Request(
"https://desk.example.test/original",
headers={"X-OTRS-Header-SessionID": "secret"},
)
handler = _PolicyRedirectHandler(
label="Service-desk URL",
sensitive_headers=("X-OTRS-Header-SessionID",),
)
with patch.dict("os.environ", {"APP_ENV": "test"}), patch(
"govoplan_core.security.outbound_http.socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("127.0.0.1", 443))],
):
redirected = handler.redirect_request(
request,
None,
302,
"Found",
{},
"https://desk.example.test/final",
)
self.assertIsNotNone(redirected)
self.assertEqual("secret", redirected.get_header("X-otrs-header-sessionid"))
if __name__ == "__main__":
unittest.main()
+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
from datetime import UTC, datetime
import unittest
from govoplan_core.core.tickets import (
CAPABILITY_TICKET_CASE_ESCALATION,
CAPABILITY_TICKET_ROUTING,
TicketCaseEscalationCommand,
TicketCaseEscalationResult,
TicketRoutingPlan,
TicketRoutingRequest,
ticket_case_escalation_provider,
ticket_routing_provider,
)
class _Provider:
def route_ticket(self, session, principal, *, request):
del session, principal, request
return TicketRoutingPlan(provider_id="helpdesk", queue_ref="citizen-service")
def escalate_ticket(self, session, principal, *, command):
del session, principal, command
return TicketCaseEscalationResult(
provider_id="cases",
case_id="case-1",
case_number="CASE-1",
case_url="/cases/case-1",
)
class _Registry:
def __init__(self, capabilities):
self.capabilities = capabilities
def has_capability(self, name):
return name in self.capabilities
def capability(self, name):
return self.capabilities[name]
class TicketContractTests(unittest.TestCase):
def test_optional_providers_fail_open_when_absent(self) -> None:
registry = _Registry({})
self.assertIsNone(ticket_routing_provider(registry))
self.assertIsNone(ticket_case_escalation_provider(registry))
def test_optional_providers_resolve_structurally(self) -> None:
provider = _Provider()
registry = _Registry(
{
CAPABILITY_TICKET_ROUTING: provider,
CAPABILITY_TICKET_CASE_ESCALATION: provider,
}
)
self.assertIs(provider, ticket_routing_provider(registry))
self.assertIs(provider, ticket_case_escalation_provider(registry))
def test_commands_validate_tenant_time_and_relative_case_link(self) -> None:
instant = datetime(2026, 8, 22, 9, 0, tzinfo=UTC)
request = TicketRoutingRequest(
tenant_id="tenant-1",
ticket_id="ticket-1",
ticket_type="request",
priority="normal",
title="Broken streetlight",
received_at=instant,
)
self.assertEqual("ticket-1", request.ticket_id)
command = TicketCaseEscalationCommand(
tenant_id="tenant-1",
ticket_id="ticket-1",
ticket_number="TKT-1",
title="Broken streetlight",
case_type_key="service-request",
occurred_at=instant,
idempotency_key="escalation-1",
)
self.assertEqual("service-request", command.case_type_key)
with self.assertRaises(ValueError):
TicketCaseEscalationResult(
provider_id="cases",
case_id="case-1",
case_number="CASE-1",
case_url="https://other.example/cases/1",
)
if __name__ == "__main__":
unittest.main()
+86 -22
View File
@@ -1,12 +1,12 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.29",
"version": "0.1.33",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govoplan/core-webui",
"version": "0.1.29",
"version": "0.1.33",
"dependencies": {
"@govoplan/access-webui": "file:../../govoplan-access/webui",
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
@@ -27,6 +27,7 @@
"@govoplan/files-webui": "file:../../govoplan-files/webui",
"@govoplan/forms-runtime-webui": "file:../../govoplan-forms-runtime/webui",
"@govoplan/forms-webui": "file:../../govoplan-forms/webui",
"@govoplan/helpdesk-webui": "file:../../govoplan-helpdesk/webui",
"@govoplan/identity-trust-webui": "file:../../govoplan-identity-trust/webui",
"@govoplan/identity-webui": "file:../../govoplan-identity/webui",
"@govoplan/idm-webui": "file:../../govoplan-idm/webui",
@@ -48,8 +49,10 @@
"@govoplan/tasks-webui": "file:../../govoplan-tasks/webui",
"@govoplan/templates-webui": "file:../../govoplan-templates/webui",
"@govoplan/tenancy-webui": "file:../../govoplan-tenancy/webui",
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
"@govoplan/views-webui": "file:../../govoplan-views/webui",
"@govoplan/voting-webui": "file:../../govoplan-voting/webui",
"@govoplan/wiki-webui": "file:../../govoplan-wiki/webui",
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
"@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2",
@@ -151,7 +154,7 @@
},
"../../govoplan-audit/webui": {
"name": "@govoplan/audit-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -186,7 +189,7 @@
},
"../../govoplan-campaign/webui": {
"name": "@govoplan/campaign-webui",
"version": "0.1.22",
"version": "0.1.24",
"dependencies": {
"read-excel-file": "9.2.0"
},
@@ -208,9 +211,9 @@
},
"../../govoplan-cases/webui": {
"name": "@govoplan/cases-webui",
"version": "0.1.18",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@govoplan/core-webui": "^0.1.30",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
@@ -240,7 +243,7 @@
},
"../../govoplan-connectors/webui": {
"name": "@govoplan/connectors-webui",
"version": "0.1.18",
"version": "0.1.22",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"react": ">=19.2.7 <20",
@@ -270,7 +273,7 @@
},
"../../govoplan-dataflow/webui": {
"name": "@govoplan/dataflow-webui",
"version": "0.1.18",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@xyflow/react": "^12.11.2",
@@ -322,7 +325,7 @@
},
"../../govoplan-docs/webui": {
"name": "@govoplan/docs-webui",
"version": "0.1.18",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0",
@@ -391,12 +394,29 @@
},
"../../govoplan-forms/webui": {
"name": "@govoplan/forms-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20"
"react-dom": ">=19.2.7 <20",
"react-router": ">=8.3.0 <9"
},
"peerDependenciesMeta": {
"@govoplan/core-webui": {
"optional": true
}
}
},
"../../govoplan-helpdesk/webui": {
"name": "@govoplan/helpdesk-webui",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.30",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
"react-router": ">=8.3.0 <9"
},
"peerDependenciesMeta": {
"@govoplan/core-webui": {
@@ -436,7 +456,7 @@
},
"../../govoplan-idm/webui": {
"name": "@govoplan/idm-webui",
"version": "0.1.19",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0",
@@ -455,7 +475,7 @@
},
"../../govoplan-mail/webui": {
"name": "@govoplan/mail-webui",
"version": "0.1.18",
"version": "0.1.21",
"devDependencies": {
"typescript": "^5.7.2"
},
@@ -493,7 +513,7 @@
},
"../../govoplan-ops/webui": {
"name": "@govoplan/ops-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0",
@@ -547,7 +567,7 @@
},
"../../govoplan-policy/webui": {
"name": "@govoplan/policy-webui",
"version": "0.1.19",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -563,7 +583,7 @@
},
"../../govoplan-portal/webui": {
"name": "@govoplan/portal-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -579,7 +599,7 @@
},
"../../govoplan-postbox/webui": {
"name": "@govoplan/postbox-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -611,7 +631,7 @@
},
"../../govoplan-quick-access/webui": {
"name": "@govoplan/quick-access-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -675,7 +695,7 @@
},
"../../govoplan-scheduling/webui": {
"name": "@govoplan/scheduling-webui",
"version": "0.1.19",
"version": "0.1.18",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0",
@@ -710,7 +730,7 @@
},
"../../govoplan-tasks/webui": {
"name": "@govoplan/tasks-webui",
"version": "0.1.19",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -756,9 +776,25 @@
}
}
},
"../../govoplan-tickets/webui": {
"name": "@govoplan/tickets-webui",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.30",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
"react-router": ">=8.3.0 <9"
},
"peerDependenciesMeta": {
"@govoplan/core-webui": {
"optional": true
}
}
},
"../../govoplan-views/webui": {
"name": "@govoplan/views-webui",
"version": "0.1.18",
"version": "0.1.19",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0",
@@ -787,9 +823,25 @@
}
}
},
"../../govoplan-wiki/webui": {
"name": "@govoplan/wiki-webui",
"version": "0.1.20",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.31",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
"react-router": ">=8.3.0 <9"
},
"peerDependenciesMeta": {
"@govoplan/core-webui": {
"optional": true
}
}
},
"../../govoplan-workflow/webui": {
"name": "@govoplan/workflow-webui",
"version": "0.1.18",
"version": "0.1.21",
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@xyflow/react": "^12.11.2",
@@ -1633,6 +1685,10 @@
"resolved": "../../govoplan-forms/webui",
"link": true
},
"node_modules/@govoplan/helpdesk-webui": {
"resolved": "../../govoplan-helpdesk/webui",
"link": true
},
"node_modules/@govoplan/identity-trust-webui": {
"resolved": "../../govoplan-identity-trust/webui",
"link": true
@@ -1717,6 +1773,10 @@
"resolved": "../../govoplan-tenancy/webui",
"link": true
},
"node_modules/@govoplan/tickets-webui": {
"resolved": "../../govoplan-tickets/webui",
"link": true
},
"node_modules/@govoplan/views-webui": {
"resolved": "../../govoplan-views/webui",
"link": true
@@ -1725,6 +1785,10 @@
"resolved": "../../govoplan-voting/webui",
"link": true
},
"node_modules/@govoplan/wiki-webui": {
"resolved": "../../govoplan-wiki/webui",
"link": true
},
"node_modules/@govoplan/workflow-webui": {
"resolved": "../../govoplan-workflow/webui",
"link": true
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.29",
"version": "0.1.33",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -77,6 +77,7 @@
"@govoplan/files-webui": "file:../../govoplan-files/webui",
"@govoplan/forms-runtime-webui": "file:../../govoplan-forms-runtime/webui",
"@govoplan/forms-webui": "file:../../govoplan-forms/webui",
"@govoplan/helpdesk-webui": "file:../../govoplan-helpdesk/webui",
"@govoplan/identity-trust-webui": "file:../../govoplan-identity-trust/webui",
"@govoplan/identity-webui": "file:../../govoplan-identity/webui",
"@govoplan/idm-webui": "file:../../govoplan-idm/webui",
@@ -98,8 +99,10 @@
"@govoplan/tasks-webui": "file:../../govoplan-tasks/webui",
"@govoplan/templates-webui": "file:../../govoplan-templates/webui",
"@govoplan/tenancy-webui": "file:../../govoplan-tenancy/webui",
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
"@govoplan/views-webui": "file:../../govoplan-views/webui",
"@govoplan/voting-webui": "file:../../govoplan-voting/webui",
"@govoplan/wiki-webui": "file:../../govoplan-wiki/webui",
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
"@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2",
+5 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/core-webui",
"version": "0.1.27",
"version": "0.1.33",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -30,15 +30,19 @@
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.18",
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.18",
"@govoplan/cases-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-cases.git#v0.1.20",
"@govoplan/dashboard-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git#v0.1.18",
"@govoplan/docs-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git#v0.1.18",
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.20",
"@govoplan/helpdesk-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-helpdesk.git#v0.1.20",
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.19",
"@govoplan/mail-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git#v0.1.18",
"@govoplan/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.22",
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.18",
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.18",
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.18",
"@govoplan/tickets-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tickets.git#v0.1.20",
"@govoplan/wiki-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-wiki.git#v0.1.20",
"@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2",
"@tiptap/pm": "^3.29.2",
+8 -1
View File
@@ -22,6 +22,7 @@ const packageByModule = {
files: "@govoplan/files-webui",
forms: "@govoplan/forms-webui",
forms_runtime: "@govoplan/forms-runtime-webui",
helpdesk: "@govoplan/helpdesk-webui",
idm: "@govoplan/idm-webui",
identity: "@govoplan/identity-webui",
mail: "@govoplan/mail-webui",
@@ -42,8 +43,10 @@ const packageByModule = {
tasks: "@govoplan/tasks-webui",
tenancy: "@govoplan/tenancy-webui",
templates: "@govoplan/templates-webui",
tickets: "@govoplan/tickets-webui",
views: "@govoplan/views-webui",
voting: "@govoplan/voting-webui",
wiki: "@govoplan/wiki-webui",
workflow: "@govoplan/workflow-webui"
};
@@ -75,6 +78,10 @@ const cases = [
{ name: "files-only", modules: ["files"] },
{ name: "forms-only", modules: ["forms"] },
{ name: "forms-runtime", modules: ["forms", "forms_runtime"] },
{ name: "tickets-only", modules: ["tickets"] },
{ name: "tickets-with-helpdesk-and-cases", modules: ["tickets", "helpdesk", "cases"] },
{ name: "wiki-only", modules: ["wiki"] },
{ name: "wiki-with-files-search", modules: ["wiki", "files", "search"] },
{ name: "mail-only", modules: ["mail"] },
{ name: "notifications-only", modules: ["notifications"] },
{ name: "organizations-only", modules: ["organizations"] },
@@ -106,7 +113,7 @@ const cases = [
{ name: "tasks-only", modules: ["access", "tasks"] },
{ name: "tasks-with-contributors", modules: ["access", "approvals", "postbox", "workflow", "dashboard", "tasks"] },
{ name: "voting-only", modules: ["access", "voting"] },
{ name: "full-product", modules: ["access", "tenancy", "admin", "addresses", "approvals", "policy", "audit", "dashboard", "datasources", "dataflow", "dist_lists", "templates", "workflow", "views", "organizations", "idm", "identity", "identity_trust", "encryption", "cases", "committee", "connectors", "campaigns", "files", "forms", "forms_runtime", "mail", "notifications", "docs", "ops", "payments", "calendar", "scheduling", "portal", "postbox", "projects", "quick_access", "reporting", "records", "risk_compliance", "search", "tasks", "voting"] }
{ name: "full-product", modules: ["access", "tenancy", "admin", "addresses", "approvals", "policy", "audit", "dashboard", "datasources", "dataflow", "dist_lists", "templates", "workflow", "views", "organizations", "idm", "identity", "identity_trust", "encryption", "cases", "committee", "connectors", "campaigns", "files", "forms", "forms_runtime", "helpdesk", "mail", "notifications", "docs", "ops", "payments", "calendar", "scheduling", "portal", "postbox", "projects", "quick_access", "reporting", "records", "risk_compliance", "search", "tasks", "tickets", "voting", "wiki"] }
];
const npmExec = process.env.npm_execpath;
+6
View File
@@ -32,6 +32,7 @@ const defaultWebModulePackages = [
"@govoplan/files-webui",
"@govoplan/forms-webui",
"@govoplan/forms-runtime-webui",
"@govoplan/helpdesk-webui",
"@govoplan/idm-webui",
"@govoplan/identity-webui",
"@govoplan/identity-trust-webui",
@@ -52,8 +53,10 @@ const defaultWebModulePackages = [
"@govoplan/search-webui",
"@govoplan/tenancy-webui",
"@govoplan/templates-webui",
"@govoplan/tickets-webui",
"@govoplan/views-webui",
"@govoplan/voting-webui",
"@govoplan/wiki-webui",
"@govoplan/workflow-webui"
];
@@ -266,6 +269,7 @@ export default defineConfig({
fileURLToPath(new URL('../../govoplan-files/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-forms/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-forms-runtime/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-helpdesk/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-idm/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-identity/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-mail/webui', import.meta.url)),
@@ -283,8 +287,10 @@ export default defineConfig({
fileURLToPath(new URL('../../govoplan-search/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-tenancy/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-templates/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-tickets/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-views/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-voting/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-wiki/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-workflow/webui', import.meta.url))
]
},