Compare commits

...
5 Commits
Author SHA1 Message Date
zemion aa91063211 Release Core v0.1.34 with JMAP mail contracts
Module Package Release / publish-packages (push) Successful in 14s
2026-08-22 17:08:54 +02:00
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
15 changed files with 1066 additions and 442 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. | | 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. | | 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. | | 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. | | 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. | | 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. | | 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] [project]
name = "govoplan-core" name = "govoplan-core"
version = "0.1.29" version = "0.1.34"
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components." description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" 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",
]
+43 -4
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import urllib.parse import urllib.parse
import urllib.request import urllib.request
from collections.abc import Iterable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Mapping 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) @dataclass(frozen=True, slots=True)
class HttpFetchResponse: class HttpFetchResponse:
status: int status: int
@@ -46,15 +53,27 @@ def fetch_http(
label: str = "URL", label: str = "URL",
method: str = "GET", method: str = "GET",
headers: Mapping[str, str] | None = None, headers: Mapping[str, str] | None = None,
body: bytes | None = None,
max_bytes: int | None = None, max_bytes: int | None = None,
redirect_sensitive_headers: Iterable[str] = (),
) -> HttpFetchResponse: ) -> 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) validated_url = validate_outbound_http_url(url, label=label)
request = urllib.request.Request( # noqa: S310 - URL is restricted to validated HTTP(S). request = urllib.request.Request( # noqa: S310 - URL is restricted to validated HTTP(S).
validated_url, validated_url,
data=body,
headers=dict(headers or {}), headers=dict(headers or {}),
method=method, 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 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()) response_headers = dict(response.headers.items())
return HttpFetchResponse( return HttpFetchResponse(
@@ -76,16 +95,35 @@ def fetch_http_text(
label: str = "URL", label: str = "URL",
method: str = "GET", method: str = "GET",
headers: Mapping[str, str] | None = None, headers: Mapping[str, str] | None = None,
body: bytes | None = None,
encoding: str = "utf-8", encoding: str = "utf-8",
max_bytes: int | None = None, max_bytes: int | None = None,
redirect_sensitive_headers: Iterable[str] = (),
) -> 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): class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
def __init__(self, *, label: str) -> None: def __init__(
self,
*,
label: str,
sensitive_headers: Iterable[str] = (),
) -> None:
super().__init__() super().__init__()
self._label = label 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] 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") candidate = validate_outbound_http_url(newurl, label=f"{self._label} redirect")
@@ -95,7 +133,8 @@ class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
return None return None
new_request = super().redirect_request(req, fp, code, msg, headers, candidate) new_request = super().redirect_request(req, fp, code, msg, headers, candidate)
if new_request is not None and _http_origin(previous) != _http_origin(redirected): if new_request is not None and _http_origin(previous) != _http_origin(redirected):
for header in ("Authorization", "Proxy-Authorization", "Cookie", "Cookie2"): for header in tuple(new_request.headers) + tuple(new_request.unredirected_hdrs):
if header.lower() in self._sensitive_headers:
new_request.remove_header(header) new_request.remove_header(header)
return new_request return new_request
+91 -4
View File
@@ -2,9 +2,14 @@ from __future__ import annotations
import io import io
import unittest 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 ( from govoplan_core.security.outbound_http import (
DEFAULT_FILE_TRANSFER_BYTES, DEFAULT_FILE_TRANSFER_BYTES,
DEFAULT_STRUCTURED_RESPONSE_BYTES, DEFAULT_STRUCTURED_RESPONSE_BYTES,
@@ -21,6 +26,51 @@ from govoplan_core.security.outbound_http import (
class HttpFetchTests(unittest.TestCase): 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: 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.assertEqual("https://example.test/catalog.json", validate_http_url("https://example.test/catalog.json"))
self.assertTrue(is_http_url("http://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( request = urllib.request.Request(
"https://catalog.example.test/releases", "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( with patch.dict("os.environ", {"APP_ENV": "test"}), patch(
"govoplan_core.security.outbound_http.socket.getaddrinfo", "govoplan_core.security.outbound_http.socket.getaddrinfo",
return_value=[(2, 1, 6, "", ("127.0.0.1", 443))], return_value=[(2, 1, 6, "", ("127.0.0.1", 443))],
@@ -215,9 +273,38 @@ class HttpFetchTests(unittest.TestCase):
self.assertIsNotNone(redirected) self.assertIsNotNone(redirected)
self.assertIsNone(redirected.get_header("Authorization")) 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.assertEqual("request-1", redirected.get_header("X-request-id"))
self.assertIsNone(downgrade) 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__": if __name__ == "__main__":
unittest.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", "name": "@govoplan/core-webui",
"version": "0.1.29", "version": "0.1.34",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.29", "version": "0.1.34",
"dependencies": { "dependencies": {
"@govoplan/access-webui": "file:../../govoplan-access/webui", "@govoplan/access-webui": "file:../../govoplan-access/webui",
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui", "@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
@@ -27,6 +27,7 @@
"@govoplan/files-webui": "file:../../govoplan-files/webui", "@govoplan/files-webui": "file:../../govoplan-files/webui",
"@govoplan/forms-runtime-webui": "file:../../govoplan-forms-runtime/webui", "@govoplan/forms-runtime-webui": "file:../../govoplan-forms-runtime/webui",
"@govoplan/forms-webui": "file:../../govoplan-forms/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-trust-webui": "file:../../govoplan-identity-trust/webui",
"@govoplan/identity-webui": "file:../../govoplan-identity/webui", "@govoplan/identity-webui": "file:../../govoplan-identity/webui",
"@govoplan/idm-webui": "file:../../govoplan-idm/webui", "@govoplan/idm-webui": "file:../../govoplan-idm/webui",
@@ -48,8 +49,10 @@
"@govoplan/tasks-webui": "file:../../govoplan-tasks/webui", "@govoplan/tasks-webui": "file:../../govoplan-tasks/webui",
"@govoplan/templates-webui": "file:../../govoplan-templates/webui", "@govoplan/templates-webui": "file:../../govoplan-templates/webui",
"@govoplan/tenancy-webui": "file:../../govoplan-tenancy/webui", "@govoplan/tenancy-webui": "file:../../govoplan-tenancy/webui",
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
"@govoplan/views-webui": "file:../../govoplan-views/webui", "@govoplan/views-webui": "file:../../govoplan-views/webui",
"@govoplan/voting-webui": "file:../../govoplan-voting/webui", "@govoplan/voting-webui": "file:../../govoplan-voting/webui",
"@govoplan/wiki-webui": "file:../../govoplan-wiki/webui",
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui", "@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
"@tiptap/core": "^3.29.2", "@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2", "@tiptap/extension-image": "^3.29.2",
@@ -151,7 +154,7 @@
}, },
"../../govoplan-audit/webui": { "../../govoplan-audit/webui": {
"name": "@govoplan/audit-webui", "name": "@govoplan/audit-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
@@ -186,7 +189,7 @@
}, },
"../../govoplan-campaign/webui": { "../../govoplan-campaign/webui": {
"name": "@govoplan/campaign-webui", "name": "@govoplan/campaign-webui",
"version": "0.1.22", "version": "0.1.24",
"dependencies": { "dependencies": {
"read-excel-file": "9.2.0" "read-excel-file": "9.2.0"
}, },
@@ -208,9 +211,9 @@
}, },
"../../govoplan-cases/webui": { "../../govoplan-cases/webui": {
"name": "@govoplan/cases-webui", "name": "@govoplan/cases-webui",
"version": "0.1.18", "version": "0.1.20",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.30",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": ">=19.2.7 <20", "react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20", "react-dom": ">=19.2.7 <20",
@@ -240,7 +243,7 @@
}, },
"../../govoplan-connectors/webui": { "../../govoplan-connectors/webui": {
"name": "@govoplan/connectors-webui", "name": "@govoplan/connectors-webui",
"version": "0.1.18", "version": "0.1.22",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"react": ">=19.2.7 <20", "react": ">=19.2.7 <20",
@@ -270,7 +273,7 @@
}, },
"../../govoplan-dataflow/webui": { "../../govoplan-dataflow/webui": {
"name": "@govoplan/dataflow-webui", "name": "@govoplan/dataflow-webui",
"version": "0.1.18", "version": "0.1.20",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"@xyflow/react": "^12.11.2", "@xyflow/react": "^12.11.2",
@@ -322,7 +325,7 @@
}, },
"../../govoplan-docs/webui": { "../../govoplan-docs/webui": {
"name": "@govoplan/docs-webui", "name": "@govoplan/docs-webui",
"version": "0.1.18", "version": "0.1.20",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0", "@vitejs/plugin-react": "^5.2.0",
@@ -391,12 +394,29 @@
}, },
"../../govoplan-forms/webui": { "../../govoplan-forms/webui": {
"name": "@govoplan/forms-webui", "name": "@govoplan/forms-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": ">=19.2.7 <20", "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": { "peerDependenciesMeta": {
"@govoplan/core-webui": { "@govoplan/core-webui": {
@@ -436,7 +456,7 @@
}, },
"../../govoplan-idm/webui": { "../../govoplan-idm/webui": {
"name": "@govoplan/idm-webui", "name": "@govoplan/idm-webui",
"version": "0.1.19", "version": "0.1.20",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0", "@vitejs/plugin-react": "^5.2.0",
@@ -455,7 +475,7 @@
}, },
"../../govoplan-mail/webui": { "../../govoplan-mail/webui": {
"name": "@govoplan/mail-webui", "name": "@govoplan/mail-webui",
"version": "0.1.18", "version": "0.1.22",
"devDependencies": { "devDependencies": {
"typescript": "^5.7.2" "typescript": "^5.7.2"
}, },
@@ -493,7 +513,7 @@
}, },
"../../govoplan-ops/webui": { "../../govoplan-ops/webui": {
"name": "@govoplan/ops-webui", "name": "@govoplan/ops-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0", "@vitejs/plugin-react": "^5.2.0",
@@ -547,7 +567,7 @@
}, },
"../../govoplan-policy/webui": { "../../govoplan-policy/webui": {
"name": "@govoplan/policy-webui", "name": "@govoplan/policy-webui",
"version": "0.1.19", "version": "0.1.20",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
@@ -563,7 +583,7 @@
}, },
"../../govoplan-portal/webui": { "../../govoplan-portal/webui": {
"name": "@govoplan/portal-webui", "name": "@govoplan/portal-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
@@ -579,7 +599,7 @@
}, },
"../../govoplan-postbox/webui": { "../../govoplan-postbox/webui": {
"name": "@govoplan/postbox-webui", "name": "@govoplan/postbox-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
@@ -611,7 +631,7 @@
}, },
"../../govoplan-quick-access/webui": { "../../govoplan-quick-access/webui": {
"name": "@govoplan/quick-access-webui", "name": "@govoplan/quick-access-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
@@ -675,7 +695,7 @@
}, },
"../../govoplan-scheduling/webui": { "../../govoplan-scheduling/webui": {
"name": "@govoplan/scheduling-webui", "name": "@govoplan/scheduling-webui",
"version": "0.1.19", "version": "0.1.18",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"@vitejs/plugin-react": "^5.2.0", "@vitejs/plugin-react": "^5.2.0",
@@ -710,7 +730,7 @@
}, },
"../../govoplan-tasks/webui": { "../../govoplan-tasks/webui": {
"name": "@govoplan/tasks-webui", "name": "@govoplan/tasks-webui",
"version": "0.1.19", "version": "0.1.20",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "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": { "../../govoplan-views/webui": {
"name": "@govoplan/views-webui", "name": "@govoplan/views-webui",
"version": "0.1.18", "version": "0.1.19",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"lucide-react": "^1.23.0", "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": { "../../govoplan-workflow/webui": {
"name": "@govoplan/workflow-webui", "name": "@govoplan/workflow-webui",
"version": "0.1.18", "version": "0.1.21",
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.18", "@govoplan/core-webui": "^0.1.18",
"@xyflow/react": "^12.11.2", "@xyflow/react": "^12.11.2",
@@ -1633,6 +1685,10 @@
"resolved": "../../govoplan-forms/webui", "resolved": "../../govoplan-forms/webui",
"link": true "link": true
}, },
"node_modules/@govoplan/helpdesk-webui": {
"resolved": "../../govoplan-helpdesk/webui",
"link": true
},
"node_modules/@govoplan/identity-trust-webui": { "node_modules/@govoplan/identity-trust-webui": {
"resolved": "../../govoplan-identity-trust/webui", "resolved": "../../govoplan-identity-trust/webui",
"link": true "link": true
@@ -1717,6 +1773,10 @@
"resolved": "../../govoplan-tenancy/webui", "resolved": "../../govoplan-tenancy/webui",
"link": true "link": true
}, },
"node_modules/@govoplan/tickets-webui": {
"resolved": "../../govoplan-tickets/webui",
"link": true
},
"node_modules/@govoplan/views-webui": { "node_modules/@govoplan/views-webui": {
"resolved": "../../govoplan-views/webui", "resolved": "../../govoplan-views/webui",
"link": true "link": true
@@ -1725,6 +1785,10 @@
"resolved": "../../govoplan-voting/webui", "resolved": "../../govoplan-voting/webui",
"link": true "link": true
}, },
"node_modules/@govoplan/wiki-webui": {
"resolved": "../../govoplan-wiki/webui",
"link": true
},
"node_modules/@govoplan/workflow-webui": { "node_modules/@govoplan/workflow-webui": {
"resolved": "../../govoplan-workflow/webui", "resolved": "../../govoplan-workflow/webui",
"link": true "link": true
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.29", "version": "0.1.34",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
@@ -77,6 +77,7 @@
"@govoplan/files-webui": "file:../../govoplan-files/webui", "@govoplan/files-webui": "file:../../govoplan-files/webui",
"@govoplan/forms-runtime-webui": "file:../../govoplan-forms-runtime/webui", "@govoplan/forms-runtime-webui": "file:../../govoplan-forms-runtime/webui",
"@govoplan/forms-webui": "file:../../govoplan-forms/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-trust-webui": "file:../../govoplan-identity-trust/webui",
"@govoplan/identity-webui": "file:../../govoplan-identity/webui", "@govoplan/identity-webui": "file:../../govoplan-identity/webui",
"@govoplan/idm-webui": "file:../../govoplan-idm/webui", "@govoplan/idm-webui": "file:../../govoplan-idm/webui",
@@ -98,8 +99,10 @@
"@govoplan/tasks-webui": "file:../../govoplan-tasks/webui", "@govoplan/tasks-webui": "file:../../govoplan-tasks/webui",
"@govoplan/templates-webui": "file:../../govoplan-templates/webui", "@govoplan/templates-webui": "file:../../govoplan-templates/webui",
"@govoplan/tenancy-webui": "file:../../govoplan-tenancy/webui", "@govoplan/tenancy-webui": "file:../../govoplan-tenancy/webui",
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
"@govoplan/views-webui": "file:../../govoplan-views/webui", "@govoplan/views-webui": "file:../../govoplan-views/webui",
"@govoplan/voting-webui": "file:../../govoplan-voting/webui", "@govoplan/voting-webui": "file:../../govoplan-voting/webui",
"@govoplan/wiki-webui": "file:../../govoplan-wiki/webui",
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui", "@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
"@tiptap/core": "^3.29.2", "@tiptap/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2", "@tiptap/extension-image": "^3.29.2",
+5 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.27", "version": "0.1.34",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "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/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/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/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/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/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/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/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/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/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/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/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/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/core": "^3.29.2",
"@tiptap/extension-image": "^3.29.2", "@tiptap/extension-image": "^3.29.2",
"@tiptap/pm": "^3.29.2", "@tiptap/pm": "^3.29.2",
+8 -1
View File
@@ -22,6 +22,7 @@ const packageByModule = {
files: "@govoplan/files-webui", files: "@govoplan/files-webui",
forms: "@govoplan/forms-webui", forms: "@govoplan/forms-webui",
forms_runtime: "@govoplan/forms-runtime-webui", forms_runtime: "@govoplan/forms-runtime-webui",
helpdesk: "@govoplan/helpdesk-webui",
idm: "@govoplan/idm-webui", idm: "@govoplan/idm-webui",
identity: "@govoplan/identity-webui", identity: "@govoplan/identity-webui",
mail: "@govoplan/mail-webui", mail: "@govoplan/mail-webui",
@@ -42,8 +43,10 @@ const packageByModule = {
tasks: "@govoplan/tasks-webui", tasks: "@govoplan/tasks-webui",
tenancy: "@govoplan/tenancy-webui", tenancy: "@govoplan/tenancy-webui",
templates: "@govoplan/templates-webui", templates: "@govoplan/templates-webui",
tickets: "@govoplan/tickets-webui",
views: "@govoplan/views-webui", views: "@govoplan/views-webui",
voting: "@govoplan/voting-webui", voting: "@govoplan/voting-webui",
wiki: "@govoplan/wiki-webui",
workflow: "@govoplan/workflow-webui" workflow: "@govoplan/workflow-webui"
}; };
@@ -75,6 +78,10 @@ const cases = [
{ name: "files-only", modules: ["files"] }, { name: "files-only", modules: ["files"] },
{ name: "forms-only", modules: ["forms"] }, { name: "forms-only", modules: ["forms"] },
{ name: "forms-runtime", modules: ["forms", "forms_runtime"] }, { 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: "mail-only", modules: ["mail"] },
{ name: "notifications-only", modules: ["notifications"] }, { name: "notifications-only", modules: ["notifications"] },
{ name: "organizations-only", modules: ["organizations"] }, { name: "organizations-only", modules: ["organizations"] },
@@ -106,7 +113,7 @@ const cases = [
{ name: "tasks-only", modules: ["access", "tasks"] }, { name: "tasks-only", modules: ["access", "tasks"] },
{ name: "tasks-with-contributors", modules: ["access", "approvals", "postbox", "workflow", "dashboard", "tasks"] }, { name: "tasks-with-contributors", modules: ["access", "approvals", "postbox", "workflow", "dashboard", "tasks"] },
{ name: "voting-only", modules: ["access", "voting"] }, { 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; const npmExec = process.env.npm_execpath;
+5 -2
View File
@@ -35,7 +35,7 @@ export type MailProfilePatternRules = Partial<Record<MailProfilePatternKey, stri
export type MailConnectionTestResponse = { export type MailConnectionTestResponse = {
ok: boolean; ok: boolean;
protocol: "smtp" | "imap"; protocol: "smtp" | "imap" | "jmap" | "pop3";
host?: string | null; host?: string | null;
port?: number | null; port?: number | null;
security?: MailSecurity | string | null; security?: MailSecurity | string | null;
@@ -52,7 +52,7 @@ export type MailImapFolderResponse = {
export type MailImapFolderListResponse = { export type MailImapFolderListResponse = {
ok: boolean; ok: boolean;
protocol: "imap"; protocol: "imap" | "jmap";
host?: string | null; host?: string | null;
port?: number | null; port?: number | null;
security?: MailSecurity | string | null; security?: MailSecurity | string | null;
@@ -69,6 +69,7 @@ export type MailImapFolderListResponse = {
export const mailProfilePatternKeys = [ export const mailProfilePatternKeys = [
"smtp_hosts", "smtp_hosts",
"imap_hosts", "imap_hosts",
"jmap_hosts",
"envelope_senders", "envelope_senders",
"from_headers", "from_headers",
"recipient_domains" "recipient_domains"
@@ -82,11 +83,13 @@ export const mailProfilePolicyLimitKeys = [
"imap_credentials.inherit", "imap_credentials.inherit",
"whitelist.smtp_hosts", "whitelist.smtp_hosts",
"whitelist.imap_hosts", "whitelist.imap_hosts",
"whitelist.jmap_hosts",
"whitelist.envelope_senders", "whitelist.envelope_senders",
"whitelist.from_headers", "whitelist.from_headers",
"whitelist.recipient_domains", "whitelist.recipient_domains",
"blacklist.smtp_hosts", "blacklist.smtp_hosts",
"blacklist.imap_hosts", "blacklist.imap_hosts",
"blacklist.jmap_hosts",
"blacklist.envelope_senders", "blacklist.envelope_senders",
"blacklist.from_headers", "blacklist.from_headers",
"blacklist.recipient_domains" "blacklist.recipient_domains"
+13 -3
View File
@@ -847,6 +847,16 @@ export type MailImapTransportSettings = MailTransportSettings & {
folder_mappings?: MailImapFolderMappings | null; folder_mappings?: MailImapFolderMappings | null;
}; };
export type MailJmapTransportSettings = {
session_url: string;
account_id?: string | null;
auth_scheme?: "bearer" | "basic";
timeout_seconds?: number | null;
max_response_bytes?: number | null;
max_body_value_bytes?: number | null;
allowed_api_origins?: string[];
};
export type MailServerProfileCredentials = { export type MailServerProfileCredentials = {
smtp?: MailTransportCredentials | null; smtp?: MailTransportCredentials | null;
imap?: MailTransportCredentials | null; imap?: MailTransportCredentials | null;
@@ -883,9 +893,9 @@ export type MailServerEndpoint = {
id: string; id: string;
profile_id: string; profile_id: string;
tenant_id?: string | null; tenant_id?: string | null;
protocol: "smtp" | "imap"; protocol: "smtp" | "imap" | "jmap" | "pop3";
name: string; name: string;
config: MailTransportSettings | MailImapTransportSettings; config: MailTransportSettings | MailImapTransportSettings | MailJmapTransportSettings;
scope_type: MailProfileScope; scope_type: MailProfileScope;
scope_id?: string | null; scope_id?: string | null;
inherit_to_lower_scopes: boolean; inherit_to_lower_scopes: boolean;
@@ -922,7 +932,7 @@ export type MailCredentialPolicy = {
allow_override?: boolean | null; allow_override?: boolean | null;
}; };
export type MailProfilePatternKey = "smtp_hosts" | "imap_hosts" | "envelope_senders" | "from_headers" | "recipient_domains"; export type MailProfilePatternKey = "smtp_hosts" | "imap_hosts" | "jmap_hosts" | "envelope_senders" | "from_headers" | "recipient_domains";
export type MailProfilePolicy = { export type MailProfilePolicy = {
allowed_profile_ids?: string[] | null; allowed_profile_ids?: string[] | null;
+6
View File
@@ -32,6 +32,7 @@ const defaultWebModulePackages = [
"@govoplan/files-webui", "@govoplan/files-webui",
"@govoplan/forms-webui", "@govoplan/forms-webui",
"@govoplan/forms-runtime-webui", "@govoplan/forms-runtime-webui",
"@govoplan/helpdesk-webui",
"@govoplan/idm-webui", "@govoplan/idm-webui",
"@govoplan/identity-webui", "@govoplan/identity-webui",
"@govoplan/identity-trust-webui", "@govoplan/identity-trust-webui",
@@ -52,8 +53,10 @@ const defaultWebModulePackages = [
"@govoplan/search-webui", "@govoplan/search-webui",
"@govoplan/tenancy-webui", "@govoplan/tenancy-webui",
"@govoplan/templates-webui", "@govoplan/templates-webui",
"@govoplan/tickets-webui",
"@govoplan/views-webui", "@govoplan/views-webui",
"@govoplan/voting-webui", "@govoplan/voting-webui",
"@govoplan/wiki-webui",
"@govoplan/workflow-webui" "@govoplan/workflow-webui"
]; ];
@@ -266,6 +269,7 @@ export default defineConfig({
fileURLToPath(new URL('../../govoplan-files/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-files/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-forms/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-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-idm/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-identity/webui', import.meta.url)), fileURLToPath(new URL('../../govoplan-identity/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-mail/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-search/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-tenancy/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-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-views/webui', import.meta.url)),
fileURLToPath(new URL('../../govoplan-voting/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)) fileURLToPath(new URL('../../govoplan-workflow/webui', import.meta.url))
] ]
}, },