Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c1358b862 | ||
|
|
a9035c4c3b | ||
|
|
137c7c005f | ||
|
|
8eeea968f2 | ||
|
|
0ca6568005 | ||
|
|
af90db44c9 | ||
|
|
5de46e9c0e | ||
|
|
1d9b677c1b | ||
|
|
54178ee56c | ||
|
|
10e7597612 |
@@ -14,6 +14,7 @@ consistent while each module still owns its domain rules.
|
|||||||
| Governance defaults | `govoplan-admin` plus `govoplan-access` materializer | admin settings, governance template routes, access materialization capability | System governance can block tenant-local groups, roles, and API keys. |
|
| Governance defaults | `govoplan-admin` plus `govoplan-access` materializer | admin settings, governance template routes, access materialization capability | System governance can block tenant-local groups, roles, and API keys. |
|
||||||
| Delegation and ownership policy | access/campaign/mail/files modules | capability checks and owner-scoped APIs | Source provenance should use this contract when policies become externally explainable. |
|
| Delegation and ownership policy | access/campaign/mail/files modules | capability checks and owner-scoped APIs | Source provenance should use this contract when policies become externally explainable. |
|
||||||
| Definition governance | `govoplan-policy` | capability `policy.definitionGovernance` | Resolves view, edit, run/start, reuse, derive, and automate for system, tenant, group, and user Dataflow/Workflow definitions. |
|
| Definition governance | `govoplan-policy` | capability `policy.definitionGovernance` | Resolves view, edit, run/start, reuse, derive, and automate for system, tenant, group, and user Dataflow/Workflow definitions. |
|
||||||
|
| Function assignment governance | `govoplan-policy` | capability `policy.functionAssignmentGovernance` | Returns current review steps, delegation depth/validity ceilings, and explicit timed-escalation targets consumed by IDM. |
|
||||||
|
|
||||||
## Policy Decision
|
## Policy Decision
|
||||||
|
|
||||||
@@ -126,6 +127,22 @@ When the capability is absent, modules must not silently emulate cross-scope
|
|||||||
inheritance. Their conservative fallback is limited to local tenant
|
inheritance. Their conservative fallback is limited to local tenant
|
||||||
definitions and disables reuse, derivation, and automation.
|
definitions and disables reuse, derivation, and automation.
|
||||||
|
|
||||||
|
## Function Assignment Delegation And Escalation
|
||||||
|
|
||||||
|
`FunctionAssignmentGovernanceDecision` is the versioned cross-module contract
|
||||||
|
for request/grant review. In addition to the required holder, authority, and
|
||||||
|
recipient steps, it returns `delegation_allowed`,
|
||||||
|
`maximum_delegation_depth`, `maximum_delegated_validity_days`, and typed
|
||||||
|
`FunctionAssignmentEscalationRule` entries. Each escalation entry binds one
|
||||||
|
review step to an exact target function and timeout.
|
||||||
|
|
||||||
|
The decision is a current ceiling, not durable authorization. IDM must recheck
|
||||||
|
the complete assignment-source chain and all recorded decisions before final
|
||||||
|
application. An elapsed timeout creates explicit state and evidence; it must
|
||||||
|
never be interpreted as approval or as permission to silently substitute an
|
||||||
|
approver. Missing providers, malformed rules, invalid chains, or tightened
|
||||||
|
limits fail closed with an explainable reason.
|
||||||
|
|
||||||
## Bounded Impact-Subject Providers
|
## Bounded Impact-Subject Providers
|
||||||
|
|
||||||
Policy impact previews discover optional subject providers through capability
|
Policy impact previews discover optional subject providers through capability
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-core"
|
name = "govoplan-core"
|
||||||
version = "0.1.19"
|
version = "0.1.29"
|
||||||
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"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from collections.abc import Callable, Iterable, Mapping
|
from collections.abc import Callable, Iterable, Mapping
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Protocol, runtime_checkable
|
from typing import Literal, Protocol, runtime_checkable
|
||||||
|
|
||||||
|
|
||||||
CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT = "campaigns.mailPolicyContext"
|
CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT = "campaigns.mailPolicyContext"
|
||||||
@@ -12,6 +12,20 @@ CAPABILITY_CAMPAIGNS_POLICY_CONTEXT = "campaigns.policyContext"
|
|||||||
CAPABILITY_CAMPAIGNS_DELIVERY_TASKS = "campaigns.deliveryTasks"
|
CAPABILITY_CAMPAIGNS_DELIVERY_TASKS = "campaigns.deliveryTasks"
|
||||||
CAPABILITY_CAMPAIGNS_SCHEDULES = "campaigns.schedules"
|
CAPABILITY_CAMPAIGNS_SCHEDULES = "campaigns.schedules"
|
||||||
CAPABILITY_CAMPAIGNS_RETENTION = "campaigns.retention"
|
CAPABILITY_CAMPAIGNS_RETENTION = "campaigns.retention"
|
||||||
|
CAPABILITY_CAMPAIGNS_WORK_ORCHESTRATION = "campaigns.workOrchestration"
|
||||||
|
|
||||||
|
CampaignWorkAssigneeKind = Literal[
|
||||||
|
"account",
|
||||||
|
"group",
|
||||||
|
"organization_function",
|
||||||
|
]
|
||||||
|
CampaignWorkHandoffStatus = Literal[
|
||||||
|
"open",
|
||||||
|
"in_progress",
|
||||||
|
"completed",
|
||||||
|
"rejected",
|
||||||
|
"cancelled",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -32,6 +46,88 @@ class CampaignPolicyContext:
|
|||||||
settings: Mapping[str, object] = field(default_factory=dict)
|
settings: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class CampaignWorkHandoffRequest:
|
||||||
|
"""Typed request used by Workflow to open accountable Campaign work."""
|
||||||
|
|
||||||
|
tenant_id: str
|
||||||
|
idempotency_key: str
|
||||||
|
purpose: str
|
||||||
|
assignee_kind: CampaignWorkAssigneeKind
|
||||||
|
assignee_id: str
|
||||||
|
campaign_id: str | None = None
|
||||||
|
create_external_id: str | None = None
|
||||||
|
create_name: str | None = None
|
||||||
|
create_description: str | None = None
|
||||||
|
expected_campaign_revision: int | None = None
|
||||||
|
due_at: datetime | None = None
|
||||||
|
mirror_to_tasks: bool = True
|
||||||
|
correlation_id: str | None = None
|
||||||
|
workflow_instance_id: str | None = None
|
||||||
|
workflow_step_id: str | None = None
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
for value, label in (
|
||||||
|
(self.tenant_id, "Campaign hand-off tenant"),
|
||||||
|
(self.idempotency_key, "Campaign hand-off idempotency key"),
|
||||||
|
(self.purpose, "Campaign hand-off purpose"),
|
||||||
|
(self.assignee_id, "Campaign hand-off assignee"),
|
||||||
|
):
|
||||||
|
if not value.strip():
|
||||||
|
raise ValueError(f"{label} is required")
|
||||||
|
references_existing = bool(self.campaign_id and self.campaign_id.strip())
|
||||||
|
creates_new = bool(
|
||||||
|
self.create_external_id
|
||||||
|
and self.create_external_id.strip()
|
||||||
|
and self.create_name
|
||||||
|
and self.create_name.strip()
|
||||||
|
)
|
||||||
|
if references_existing == creates_new:
|
||||||
|
raise ValueError(
|
||||||
|
"Campaign hand-offs must either reference one campaign or "
|
||||||
|
"declare one new campaign."
|
||||||
|
)
|
||||||
|
if self.expected_campaign_revision is not None and (
|
||||||
|
self.expected_campaign_revision < 1
|
||||||
|
):
|
||||||
|
raise ValueError("Expected Campaign revisions start at one")
|
||||||
|
if self.due_at is not None and self.due_at.tzinfo is None:
|
||||||
|
raise ValueError("Campaign hand-off due dates require a timezone")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class CampaignWorkHandoffRef:
|
||||||
|
"""Stable, revision-bearing reference returned to the Workflow instance."""
|
||||||
|
|
||||||
|
tenant_id: str
|
||||||
|
campaign_id: str
|
||||||
|
campaign_version_id: str
|
||||||
|
campaign_revision: int
|
||||||
|
assignment_id: str
|
||||||
|
assignment_revision: int
|
||||||
|
status: CampaignWorkHandoffStatus
|
||||||
|
action_url: str
|
||||||
|
campaign_ref: str
|
||||||
|
assignment_ref: str
|
||||||
|
event_type: str = "campaign.work.changed"
|
||||||
|
replayed: bool = False
|
||||||
|
optional_capabilities: Mapping[str, bool] = field(default_factory=dict)
|
||||||
|
provenance: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class CampaignWorkHandoffInspection:
|
||||||
|
"""Current authorization and revision check before Workflow continuation."""
|
||||||
|
|
||||||
|
allowed: bool
|
||||||
|
status: CampaignWorkHandoffStatus | None = None
|
||||||
|
assignment_revision: int | None = None
|
||||||
|
action_url: str | None = None
|
||||||
|
assignment_ref: str | None = None
|
||||||
|
reason: str | None = None
|
||||||
|
provenance: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
class CampaignMailPolicyContextProvider(Protocol):
|
class CampaignMailPolicyContextProvider(Protocol):
|
||||||
def get_campaign_mail_policy_context(
|
def get_campaign_mail_policy_context(
|
||||||
@@ -132,3 +228,45 @@ class CampaignRetentionProvider(Protocol):
|
|||||||
policy_for_campaign_id: Callable[[str | None], object],
|
policy_for_campaign_id: Callable[[str | None], object],
|
||||||
) -> Mapping[str, Mapping[str, int]]:
|
) -> Mapping[str, Mapping[str, int]]:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class CampaignWorkOrchestrationProvider(Protocol):
|
||||||
|
"""Optional Campaign boundary for durable Workflow-owned hand-offs."""
|
||||||
|
|
||||||
|
def prepare_handoff(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
principal: object,
|
||||||
|
*,
|
||||||
|
request: CampaignWorkHandoffRequest,
|
||||||
|
) -> CampaignWorkHandoffRef:
|
||||||
|
...
|
||||||
|
|
||||||
|
def inspect_handoff(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
principal: object,
|
||||||
|
*,
|
||||||
|
tenant_id: str,
|
||||||
|
assignment_id: str,
|
||||||
|
expected_revision: int | None = None,
|
||||||
|
) -> CampaignWorkHandoffInspection:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def campaign_work_orchestration_provider(
|
||||||
|
registry: object | None,
|
||||||
|
) -> CampaignWorkOrchestrationProvider | None:
|
||||||
|
if (
|
||||||
|
registry is None
|
||||||
|
or not hasattr(registry, "has_capability")
|
||||||
|
or not registry.has_capability(CAPABILITY_CAMPAIGNS_WORK_ORCHESTRATION)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
capability = registry.capability(CAPABILITY_CAMPAIGNS_WORK_ORCHESTRATION)
|
||||||
|
return (
|
||||||
|
capability
|
||||||
|
if isinstance(capability, CampaignWorkOrchestrationProvider)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal, Protocol, runtime_checkable
|
from typing import Literal, Protocol, runtime_checkable
|
||||||
|
|
||||||
|
from govoplan_core.core.access import PrincipalRef
|
||||||
from govoplan_core.core.external_references import (
|
from govoplan_core.core.external_references import (
|
||||||
SOURCE_AUTHORITY_MODES,
|
SOURCE_AUTHORITY_MODES,
|
||||||
SourceAuthorityMode,
|
SourceAuthorityMode,
|
||||||
@@ -24,6 +25,7 @@ CAPABILITY_DATASOURCE_LIFECYCLE = "datasources.lifecycle"
|
|||||||
CAPABILITY_DATASOURCE_PUBLICATION = "datasources.publication"
|
CAPABILITY_DATASOURCE_PUBLICATION = "datasources.publication"
|
||||||
CAPABILITY_DATASOURCE_ORIGINS = "connectors.datasourceOrigins"
|
CAPABILITY_DATASOURCE_ORIGINS = "connectors.datasourceOrigins"
|
||||||
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS = "datasources.artifactBackends"
|
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS = "datasources.artifactBackends"
|
||||||
|
CAPABILITY_POLICY_DATASOURCE_VISIBILITY = "policy.datasourceVisibility"
|
||||||
|
|
||||||
DatasourceMode = Literal["live", "cached", "static"]
|
DatasourceMode = Literal["live", "cached", "static"]
|
||||||
DatasourceKind = Literal[
|
DatasourceKind = Literal[
|
||||||
@@ -38,6 +40,7 @@ DatasourceKind = Literal[
|
|||||||
]
|
]
|
||||||
DatasourceShape = Literal["tabular", "document", "binary", "directory", "stream"]
|
DatasourceShape = Literal["tabular", "document", "binary", "directory", "stream"]
|
||||||
DatasourceConsistency = Literal["current", "live", "frozen"]
|
DatasourceConsistency = Literal["current", "live", "frozen"]
|
||||||
|
DatasourceVisibilityAction = Literal["discover", "read"]
|
||||||
DatasourcePublicationStatus = Literal[
|
DatasourcePublicationStatus = Literal[
|
||||||
"published",
|
"published",
|
||||||
"published_with_warnings",
|
"published_with_warnings",
|
||||||
@@ -70,6 +73,7 @@ class DatasourceField:
|
|||||||
name: str
|
name: str
|
||||||
data_type: str
|
data_type: str
|
||||||
nullable: bool = True
|
nullable: bool = True
|
||||||
|
classification: str = "internal"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -90,6 +94,8 @@ class DatasourceGovernance:
|
|||||||
classification: str = "internal"
|
classification: str = "internal"
|
||||||
privacy_profile_ref: str | None = None
|
privacy_profile_ref: str | None = None
|
||||||
retention_policy_ref: str | None = None
|
retention_policy_ref: str | None = None
|
||||||
|
access_policy_ref: str | None = None
|
||||||
|
visibility_policy: Mapping[str, object] = field(default_factory=dict)
|
||||||
hold_refs: tuple[str, ...] = ()
|
hold_refs: tuple[str, ...] = ()
|
||||||
publication_state: str = "draft"
|
publication_state: str = "draft"
|
||||||
transfer_agreement_ref: str | None = None
|
transfer_agreement_ref: str | None = None
|
||||||
@@ -160,6 +166,12 @@ class DatasourceGovernance:
|
|||||||
retention_policy_ref=_optional_governance_text(
|
retention_policy_ref=_optional_governance_text(
|
||||||
source.get("retention_policy_ref")
|
source.get("retention_policy_ref")
|
||||||
),
|
),
|
||||||
|
access_policy_ref=_optional_governance_text(
|
||||||
|
source.get("access_policy_ref")
|
||||||
|
),
|
||||||
|
visibility_policy=_governance_mapping(
|
||||||
|
source.get("visibility_policy")
|
||||||
|
),
|
||||||
hold_refs=_governance_texts(source.get("hold_refs")),
|
hold_refs=_governance_texts(source.get("hold_refs")),
|
||||||
publication_state=str(source.get("publication_state") or "draft"),
|
publication_state=str(source.get("publication_state") or "draft"),
|
||||||
transfer_agreement_ref=_optional_governance_text(
|
transfer_agreement_ref=_optional_governance_text(
|
||||||
@@ -191,6 +203,8 @@ class DatasourceGovernance:
|
|||||||
"classification": self.classification,
|
"classification": self.classification,
|
||||||
"privacy_profile_ref": self.privacy_profile_ref,
|
"privacy_profile_ref": self.privacy_profile_ref,
|
||||||
"retention_policy_ref": self.retention_policy_ref,
|
"retention_policy_ref": self.retention_policy_ref,
|
||||||
|
"access_policy_ref": self.access_policy_ref,
|
||||||
|
"visibility_policy": dict(self.visibility_policy),
|
||||||
"hold_refs": list(self.hold_refs),
|
"hold_refs": list(self.hold_refs),
|
||||||
"publication_state": self.publication_state,
|
"publication_state": self.publication_state,
|
||||||
"transfer_agreement_ref": self.transfer_agreement_ref,
|
"transfer_agreement_ref": self.transfer_agreement_ref,
|
||||||
@@ -203,6 +217,39 @@ class DatasourceGovernance:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class DatasourceVisibilityPolicyRequest:
|
||||||
|
tenant_id: str
|
||||||
|
datasource_ref: str
|
||||||
|
principal: PrincipalRef
|
||||||
|
action: DatasourceVisibilityAction
|
||||||
|
classification: str = "internal"
|
||||||
|
policy_ref: str | None = None
|
||||||
|
consistency: DatasourceConsistency = "current"
|
||||||
|
materialization_ref: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class DatasourceVisibilityPolicyDecision:
|
||||||
|
allowed: bool
|
||||||
|
reason: str | None = None
|
||||||
|
policies: tuple[Mapping[str, object], ...] = ()
|
||||||
|
decision_ref: str | None = None
|
||||||
|
provenance: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class DatasourceVisibilityPolicyProvider(Protocol):
|
||||||
|
"""Optionally tighten Datasources-owned local visibility policy."""
|
||||||
|
|
||||||
|
def decide_datasource_visibility(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
*,
|
||||||
|
request: DatasourceVisibilityPolicyRequest,
|
||||||
|
) -> DatasourceVisibilityPolicyDecision: ...
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class DatasourceDescriptor:
|
class DatasourceDescriptor:
|
||||||
ref: str
|
ref: str
|
||||||
@@ -659,6 +706,13 @@ def datasource_origins(registry: object | None) -> DatasourceOriginProvider | No
|
|||||||
return capability if isinstance(capability, DatasourceOriginProvider) else None
|
return capability if isinstance(capability, DatasourceOriginProvider) else None
|
||||||
|
|
||||||
|
|
||||||
|
def datasource_visibility_policy_provider(
|
||||||
|
registry: object | None,
|
||||||
|
) -> DatasourceVisibilityPolicyProvider | None:
|
||||||
|
capability = _capability(registry, CAPABILITY_POLICY_DATASOURCE_VISIBILITY)
|
||||||
|
return capability if isinstance(capability, DatasourceVisibilityPolicyProvider) else None
|
||||||
|
|
||||||
|
|
||||||
def _capability(registry: object | None, name: str) -> object | None:
|
def _capability(registry: object | None, name: str) -> object | None:
|
||||||
if (
|
if (
|
||||||
registry is None
|
registry is None
|
||||||
@@ -695,6 +749,7 @@ __all__ = [
|
|||||||
"CAPABILITY_DATASOURCE_LIFECYCLE",
|
"CAPABILITY_DATASOURCE_LIFECYCLE",
|
||||||
"CAPABILITY_DATASOURCE_ORIGINS",
|
"CAPABILITY_DATASOURCE_ORIGINS",
|
||||||
"CAPABILITY_DATASOURCE_PUBLICATION",
|
"CAPABILITY_DATASOURCE_PUBLICATION",
|
||||||
|
"CAPABILITY_POLICY_DATASOURCE_VISIBILITY",
|
||||||
"DatasourceAccessError",
|
"DatasourceAccessError",
|
||||||
"DatasourceArtifactReference",
|
"DatasourceArtifactReference",
|
||||||
"DatasourceArtifactBackend",
|
"DatasourceArtifactBackend",
|
||||||
@@ -725,9 +780,14 @@ __all__ = [
|
|||||||
"DatasourceStageInput",
|
"DatasourceStageInput",
|
||||||
"DatasourceUnavailableError",
|
"DatasourceUnavailableError",
|
||||||
"DatasourceValidationError",
|
"DatasourceValidationError",
|
||||||
|
"DatasourceVisibilityAction",
|
||||||
|
"DatasourceVisibilityPolicyDecision",
|
||||||
|
"DatasourceVisibilityPolicyProvider",
|
||||||
|
"DatasourceVisibilityPolicyRequest",
|
||||||
"datasource_catalogue",
|
"datasource_catalogue",
|
||||||
"datasource_artifact_backend_provider",
|
"datasource_artifact_backend_provider",
|
||||||
"datasource_lifecycle",
|
"datasource_lifecycle",
|
||||||
"datasource_origins",
|
"datasource_origins",
|
||||||
"datasource_publication",
|
"datasource_publication",
|
||||||
|
"datasource_visibility_policy_provider",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ ViewGovernanceAction = Literal[
|
|||||||
"workflow_activate",
|
"workflow_activate",
|
||||||
]
|
]
|
||||||
FunctionAssignmentChangeKind = Literal["request", "grant"]
|
FunctionAssignmentChangeKind = Literal["request", "grant"]
|
||||||
|
FunctionAssignmentReviewStep = Literal["holder", "authority", "recipient"]
|
||||||
FunctionAssignmentGovernanceAction = Literal[
|
FunctionAssignmentGovernanceAction = Literal[
|
||||||
"submit",
|
"submit",
|
||||||
"approve_holder",
|
"approve_holder",
|
||||||
"approve_authority",
|
"approve_authority",
|
||||||
|
"approve_escalation",
|
||||||
"accept_recipient",
|
"accept_recipient",
|
||||||
"request_changes",
|
"request_changes",
|
||||||
"respond",
|
"respond",
|
||||||
@@ -419,6 +421,20 @@ class FunctionAssignmentGovernanceRequest:
|
|||||||
context: Mapping[str, Any] = field(default_factory=dict)
|
context: Mapping[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class FunctionAssignmentEscalationRule:
|
||||||
|
step: FunctionAssignmentReviewStep
|
||||||
|
target_function_id: str
|
||||||
|
timeout_hours: int
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"step": self.step,
|
||||||
|
"target_function_id": self.target_function_id,
|
||||||
|
"timeout_hours": self.timeout_hours,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class FunctionAssignmentGovernanceDecision:
|
class FunctionAssignmentGovernanceDecision:
|
||||||
allowed: bool
|
allowed: bool
|
||||||
@@ -431,6 +447,10 @@ class FunctionAssignmentGovernanceDecision:
|
|||||||
separation_of_duties: bool = True
|
separation_of_duties: bool = True
|
||||||
quorum: int = 1
|
quorum: int = 1
|
||||||
maximum_validity_days: int | None = None
|
maximum_validity_days: int | None = None
|
||||||
|
delegation_allowed: bool = False
|
||||||
|
maximum_delegation_depth: int = 0
|
||||||
|
maximum_delegated_validity_days: int | None = None
|
||||||
|
escalation_rules: tuple[FunctionAssignmentEscalationRule, ...] = ()
|
||||||
request_expiry_hours: int = 336
|
request_expiry_hours: int = 336
|
||||||
source_path: tuple[PolicySourceStep, ...] = ()
|
source_path: tuple[PolicySourceStep, ...] = ()
|
||||||
requirements: tuple[str, ...] = ()
|
requirements: tuple[str, ...] = ()
|
||||||
@@ -448,12 +468,24 @@ class FunctionAssignmentGovernanceDecision:
|
|||||||
"separation_of_duties": self.separation_of_duties,
|
"separation_of_duties": self.separation_of_duties,
|
||||||
"quorum": self.quorum,
|
"quorum": self.quorum,
|
||||||
"maximum_validity_days": self.maximum_validity_days,
|
"maximum_validity_days": self.maximum_validity_days,
|
||||||
|
"delegation_allowed": self.delegation_allowed,
|
||||||
|
"maximum_delegation_depth": self.maximum_delegation_depth,
|
||||||
|
"maximum_delegated_validity_days": (
|
||||||
|
self.maximum_delegated_validity_days
|
||||||
|
),
|
||||||
|
"escalation_rules": [rule.to_dict() for rule in self.escalation_rules],
|
||||||
"request_expiry_hours": self.request_expiry_hours,
|
"request_expiry_hours": self.request_expiry_hours,
|
||||||
"source_path": [step.to_dict() for step in self.source_path],
|
"source_path": [step.to_dict() for step in self.source_path],
|
||||||
"requirements": list(self.requirements),
|
"requirements": list(self.requirements),
|
||||||
"details": dict(self.details),
|
"details": dict(self.details),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def escalation_rule(
|
||||||
|
self,
|
||||||
|
step: FunctionAssignmentReviewStep,
|
||||||
|
) -> FunctionAssignmentEscalationRule | None:
|
||||||
|
return next((rule for rule in self.escalation_rules if rule.step == step), None)
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
class FunctionAssignmentGovernancePolicy(Protocol):
|
class FunctionAssignmentGovernancePolicy(Protocol):
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ from govoplan_core.core.campaigns import (
|
|||||||
CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT,
|
CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT,
|
||||||
CAPABILITY_CAMPAIGNS_POLICY_CONTEXT,
|
CAPABILITY_CAMPAIGNS_POLICY_CONTEXT,
|
||||||
CAPABILITY_CAMPAIGNS_RETENTION,
|
CAPABILITY_CAMPAIGNS_RETENTION,
|
||||||
|
CAPABILITY_CAMPAIGNS_WORK_ORCHESTRATION,
|
||||||
CampaignAccessProvider,
|
CampaignAccessProvider,
|
||||||
CampaignDeliveryTaskProvider,
|
CampaignDeliveryTaskProvider,
|
||||||
CampaignMailPolicyContext,
|
CampaignMailPolicyContext,
|
||||||
@@ -78,6 +79,10 @@ from govoplan_core.core.campaigns import (
|
|||||||
CampaignPolicyContext,
|
CampaignPolicyContext,
|
||||||
CampaignPolicyContextProvider,
|
CampaignPolicyContextProvider,
|
||||||
CampaignRetentionProvider,
|
CampaignRetentionProvider,
|
||||||
|
CampaignWorkHandoffInspection,
|
||||||
|
CampaignWorkHandoffRef,
|
||||||
|
CampaignWorkHandoffRequest,
|
||||||
|
CampaignWorkOrchestrationProvider,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.files import CAPABILITY_FILES_ACCESS, FileAccessProvider
|
from govoplan_core.core.files import CAPABILITY_FILES_ACCESS, FileAccessProvider
|
||||||
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
||||||
@@ -464,6 +469,40 @@ class _FakeCampaignRetentionProvider:
|
|||||||
return {"raw_campaign_json": {"eligible": int(dry_run)}}
|
return {"raw_campaign_json": {"eligible": int(dry_run)}}
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeCampaignWorkOrchestrationProvider:
|
||||||
|
def prepare_handoff(self, session: object, principal: object, *, request):
|
||||||
|
del session, principal
|
||||||
|
return CampaignWorkHandoffRef(
|
||||||
|
tenant_id=request.tenant_id,
|
||||||
|
campaign_id=request.campaign_id or "campaign-created",
|
||||||
|
campaign_version_id="campaign-version-1",
|
||||||
|
campaign_revision=1,
|
||||||
|
assignment_id="assignment-1",
|
||||||
|
assignment_revision=1,
|
||||||
|
status="open",
|
||||||
|
action_url="/campaigns/campaign-1/work?assignment=assignment-1",
|
||||||
|
campaign_ref="campaign:campaign-1:version:campaign-version-1:r1",
|
||||||
|
assignment_ref="campaign-work-assignment:assignment-1:r1",
|
||||||
|
)
|
||||||
|
|
||||||
|
def inspect_handoff(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
principal: object,
|
||||||
|
*,
|
||||||
|
tenant_id: str,
|
||||||
|
assignment_id: str,
|
||||||
|
expected_revision: int | None = None,
|
||||||
|
):
|
||||||
|
del session, principal, tenant_id, assignment_id
|
||||||
|
return CampaignWorkHandoffInspection(
|
||||||
|
allowed=expected_revision in {None, 1},
|
||||||
|
status="open",
|
||||||
|
assignment_revision=1,
|
||||||
|
assignment_ref="campaign-work-assignment:assignment-1:r1",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _FakeSecretProvider:
|
class _FakeSecretProvider:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._values: dict[str, str] = {}
|
self._values: dict[str, str] = {}
|
||||||
@@ -528,6 +567,10 @@ class AccessContractTests(unittest.TestCase):
|
|||||||
self.assertEqual("campaigns.mailPolicyContext", CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT)
|
self.assertEqual("campaigns.mailPolicyContext", CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT)
|
||||||
self.assertEqual("campaigns.policyContext", CAPABILITY_CAMPAIGNS_POLICY_CONTEXT)
|
self.assertEqual("campaigns.policyContext", CAPABILITY_CAMPAIGNS_POLICY_CONTEXT)
|
||||||
self.assertEqual("campaigns.retention", CAPABILITY_CAMPAIGNS_RETENTION)
|
self.assertEqual("campaigns.retention", CAPABILITY_CAMPAIGNS_RETENTION)
|
||||||
|
self.assertEqual(
|
||||||
|
"campaigns.workOrchestration",
|
||||||
|
CAPABILITY_CAMPAIGNS_WORK_ORCHESTRATION,
|
||||||
|
)
|
||||||
self.assertEqual("tenancy.tenantResolver", CAPABILITY_TENANCY_TENANT_RESOLVER)
|
self.assertEqual("tenancy.tenantResolver", CAPABILITY_TENANCY_TENANT_RESOLVER)
|
||||||
self.assertEqual("security.secretProvider", CAPABILITY_SECURITY_SECRET_PROVIDER)
|
self.assertEqual("security.secretProvider", CAPABILITY_SECURITY_SECRET_PROVIDER)
|
||||||
self.assertEqual("audit.sink", CAPABILITY_AUDIT_SINK)
|
self.assertEqual("audit.sink", CAPABILITY_AUDIT_SINK)
|
||||||
@@ -642,6 +685,10 @@ class AccessContractTests(unittest.TestCase):
|
|||||||
self.assertIsInstance(_FakeCampaignMailPolicyContextProvider(), CampaignMailPolicyContextProvider)
|
self.assertIsInstance(_FakeCampaignMailPolicyContextProvider(), CampaignMailPolicyContextProvider)
|
||||||
self.assertIsInstance(_FakeCampaignPolicyContextProvider(), CampaignPolicyContextProvider)
|
self.assertIsInstance(_FakeCampaignPolicyContextProvider(), CampaignPolicyContextProvider)
|
||||||
self.assertIsInstance(_FakeCampaignRetentionProvider(), CampaignRetentionProvider)
|
self.assertIsInstance(_FakeCampaignRetentionProvider(), CampaignRetentionProvider)
|
||||||
|
self.assertIsInstance(
|
||||||
|
_FakeCampaignWorkOrchestrationProvider(),
|
||||||
|
CampaignWorkOrchestrationProvider,
|
||||||
|
)
|
||||||
self.assertIsInstance(_FakeSecretProvider(), SecretProvider)
|
self.assertIsInstance(_FakeSecretProvider(), SecretProvider)
|
||||||
self.assertIsInstance(_FakeAuditSink(), AuditSink)
|
self.assertIsInstance(_FakeAuditSink(), AuditSink)
|
||||||
self.assertIsInstance(_FakeAuditRecorder(), AuditRecorder)
|
self.assertIsInstance(_FakeAuditRecorder(), AuditRecorder)
|
||||||
@@ -676,6 +723,37 @@ class AccessContractTests(unittest.TestCase):
|
|||||||
self.assertEqual({"job_id": "job-1", "status": "appended"}, delivery_provider.append_sent_for_job(object(), job_id="job-1"))
|
self.assertEqual({"job_id": "job-1", "status": "appended"}, delivery_provider.append_sent_for_job(object(), job_id="job-1"))
|
||||||
self.assertEqual({"raw_campaign_json": {"eligible": 1}}, retention_provider.apply_retention(object(), dry_run=True, now=object(), policy_for_campaign_id=lambda campaign_id: object()))
|
self.assertEqual({"raw_campaign_json": {"eligible": 1}}, retention_provider.apply_retention(object(), dry_run=True, now=object(), policy_for_campaign_id=lambda campaign_id: object()))
|
||||||
|
|
||||||
|
def test_campaign_work_handoff_contract_requires_one_campaign_source(self) -> None:
|
||||||
|
request = CampaignWorkHandoffRequest(
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
campaign_id="campaign-1",
|
||||||
|
idempotency_key="workflow-step-1",
|
||||||
|
purpose="Review the campaign",
|
||||||
|
assignee_kind="account",
|
||||||
|
assignee_id="account-1",
|
||||||
|
)
|
||||||
|
provider = _FakeCampaignWorkOrchestrationProvider()
|
||||||
|
|
||||||
|
handoff = provider.prepare_handoff(object(), object(), request=request)
|
||||||
|
inspection = provider.inspect_handoff(
|
||||||
|
object(),
|
||||||
|
object(),
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
assignment_id=handoff.assignment_id,
|
||||||
|
expected_revision=handoff.assignment_revision,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual("campaign-1", handoff.campaign_id)
|
||||||
|
self.assertTrue(inspection.allowed)
|
||||||
|
with self.assertRaisesRegex(ValueError, "either reference one campaign"):
|
||||||
|
CampaignWorkHandoffRequest(
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
idempotency_key="workflow-step-2",
|
||||||
|
purpose="Review",
|
||||||
|
assignee_kind="account",
|
||||||
|
assignee_id="account-1",
|
||||||
|
)
|
||||||
|
|
||||||
def test_access_capabilities_register_and_resolve_through_platform_registry(self) -> None:
|
def test_access_capabilities_register_and_resolve_through_platform_registry(self) -> None:
|
||||||
directory = _FakeAccessDirectory()
|
directory = _FakeAccessDirectory()
|
||||||
semantic_directory = _FakeAccessSemanticDirectory()
|
semantic_directory = _FakeAccessSemanticDirectory()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from govoplan_core.core.datasources import (
|
|||||||
CAPABILITY_DATASOURCE_LIFECYCLE,
|
CAPABILITY_DATASOURCE_LIFECYCLE,
|
||||||
CAPABILITY_DATASOURCE_ORIGINS,
|
CAPABILITY_DATASOURCE_ORIGINS,
|
||||||
CAPABILITY_DATASOURCE_PUBLICATION,
|
CAPABILITY_DATASOURCE_PUBLICATION,
|
||||||
|
CAPABILITY_POLICY_DATASOURCE_VISIBILITY,
|
||||||
DatasourceCatalogueProvider,
|
DatasourceCatalogueProvider,
|
||||||
DatasourceArtifactBackendProvider,
|
DatasourceArtifactBackendProvider,
|
||||||
DatasourceDescriptor,
|
DatasourceDescriptor,
|
||||||
@@ -21,11 +22,14 @@ from govoplan_core.core.datasources import (
|
|||||||
DatasourcePublicationResult,
|
DatasourcePublicationResult,
|
||||||
DatasourceReadResult,
|
DatasourceReadResult,
|
||||||
DatasourceStage,
|
DatasourceStage,
|
||||||
|
DatasourceVisibilityPolicyDecision,
|
||||||
|
DatasourceVisibilityPolicyProvider,
|
||||||
datasource_catalogue,
|
datasource_catalogue,
|
||||||
datasource_artifact_backend_provider,
|
datasource_artifact_backend_provider,
|
||||||
datasource_lifecycle,
|
datasource_lifecycle,
|
||||||
datasource_origins,
|
datasource_origins,
|
||||||
datasource_publication,
|
datasource_publication,
|
||||||
|
datasource_visibility_policy_provider,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
||||||
from govoplan_core.core.registry import PlatformRegistry
|
from govoplan_core.core.registry import PlatformRegistry
|
||||||
@@ -161,6 +165,10 @@ class _Provider:
|
|||||||
def artifact_backends(self):
|
def artifact_backends(self):
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
def decide_datasource_visibility(self, session, *, request):
|
||||||
|
del session, request
|
||||||
|
return DatasourceVisibilityPolicyDecision(allowed=True)
|
||||||
|
|
||||||
|
|
||||||
class DatasourceContractTests(unittest.TestCase):
|
class DatasourceContractTests(unittest.TestCase):
|
||||||
def test_capabilities_are_runtime_checkable_and_resolved_without_modules(self) -> None:
|
def test_capabilities_are_runtime_checkable_and_resolved_without_modules(self) -> None:
|
||||||
@@ -170,6 +178,7 @@ class DatasourceContractTests(unittest.TestCase):
|
|||||||
self.assertIsInstance(provider, DatasourcePublicationProvider)
|
self.assertIsInstance(provider, DatasourcePublicationProvider)
|
||||||
self.assertIsInstance(provider, DatasourceOriginProvider)
|
self.assertIsInstance(provider, DatasourceOriginProvider)
|
||||||
self.assertIsInstance(provider, DatasourceArtifactBackendProvider)
|
self.assertIsInstance(provider, DatasourceArtifactBackendProvider)
|
||||||
|
self.assertIsInstance(provider, DatasourceVisibilityPolicyProvider)
|
||||||
registry = PlatformRegistry()
|
registry = PlatformRegistry()
|
||||||
registry.register(
|
registry.register(
|
||||||
ModuleManifest(
|
ModuleManifest(
|
||||||
@@ -182,6 +191,7 @@ class DatasourceContractTests(unittest.TestCase):
|
|||||||
CAPABILITY_DATASOURCE_PUBLICATION: lambda context: provider,
|
CAPABILITY_DATASOURCE_PUBLICATION: lambda context: provider,
|
||||||
CAPABILITY_DATASOURCE_ORIGINS: lambda context: provider,
|
CAPABILITY_DATASOURCE_ORIGINS: lambda context: provider,
|
||||||
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS: lambda context: provider,
|
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS: lambda context: provider,
|
||||||
|
CAPABILITY_POLICY_DATASOURCE_VISIBILITY: lambda context: provider,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -192,6 +202,7 @@ class DatasourceContractTests(unittest.TestCase):
|
|||||||
self.assertIs(provider, datasource_publication(registry))
|
self.assertIs(provider, datasource_publication(registry))
|
||||||
self.assertIs(provider, datasource_origins(registry))
|
self.assertIs(provider, datasource_origins(registry))
|
||||||
self.assertIs(provider, datasource_artifact_backend_provider(registry))
|
self.assertIs(provider, datasource_artifact_backend_provider(registry))
|
||||||
|
self.assertIs(provider, datasource_visibility_policy_provider(registry))
|
||||||
self.assertIsNone(datasource_catalogue(PlatformRegistry()))
|
self.assertIsNone(datasource_catalogue(PlatformRegistry()))
|
||||||
|
|
||||||
def test_descriptor_distinguishes_mode_kind_shape_and_materialization(self) -> None:
|
def test_descriptor_distinguishes_mode_kind_shape_and_materialization(self) -> None:
|
||||||
|
|||||||
@@ -296,6 +296,9 @@ class ModuleSystemTests(unittest.TestCase):
|
|||||||
"approvals",
|
"approvals",
|
||||||
"reporting",
|
"reporting",
|
||||||
"search",
|
"search",
|
||||||
|
"organizations",
|
||||||
|
"idm",
|
||||||
|
"tasks",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(manifests["dashboard"].dependencies, ())
|
self.assertEqual(manifests["dashboard"].dependencies, ())
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from govoplan_core.core.configuration_safety import (
|
|||||||
ui_managed_configuration_fields_requiring_approval,
|
ui_managed_configuration_fields_requiring_approval,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.policy import (
|
from govoplan_core.core.policy import (
|
||||||
|
FunctionAssignmentEscalationRule,
|
||||||
|
FunctionAssignmentGovernanceDecision,
|
||||||
PolicyDecision,
|
PolicyDecision,
|
||||||
PolicySourceStep,
|
PolicySourceStep,
|
||||||
parse_policy_source_path,
|
parse_policy_source_path,
|
||||||
@@ -19,6 +21,34 @@ from govoplan_core.core.policy import (
|
|||||||
|
|
||||||
|
|
||||||
class PolicyContractTests(unittest.TestCase):
|
class PolicyContractTests(unittest.TestCase):
|
||||||
|
def test_function_assignment_policy_serializes_delegation_and_escalation(self) -> None:
|
||||||
|
decision = FunctionAssignmentGovernanceDecision(
|
||||||
|
allowed=True,
|
||||||
|
delegation_allowed=True,
|
||||||
|
maximum_delegation_depth=2,
|
||||||
|
maximum_delegated_validity_days=30,
|
||||||
|
escalation_rules=(
|
||||||
|
FunctionAssignmentEscalationRule(
|
||||||
|
step="authority",
|
||||||
|
target_function_id="function-escalation",
|
||||||
|
timeout_hours=48,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = decision.to_dict()
|
||||||
|
|
||||||
|
self.assertEqual(2, payload["maximum_delegation_depth"])
|
||||||
|
self.assertEqual(30, payload["maximum_delegated_validity_days"])
|
||||||
|
self.assertEqual(
|
||||||
|
"function-escalation",
|
||||||
|
payload["escalation_rules"][0]["target_function_id"],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
"function-escalation",
|
||||||
|
decision.escalation_rule("authority").target_function_id,
|
||||||
|
)
|
||||||
|
|
||||||
def test_policy_source_paths_are_stable_and_round_trip(self) -> None:
|
def test_policy_source_paths_are_stable_and_round_trip(self) -> None:
|
||||||
self.assertEqual(policy_source_path("system"), "system")
|
self.assertEqual(policy_source_path("system"), "system")
|
||||||
self.assertEqual(policy_source_path("tenant", "tenant-1"), "tenant:tenant-1")
|
self.assertEqual(policy_source_path("tenant", "tenant-1"), "tenant:tenant-1")
|
||||||
|
|||||||
Generated
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.19",
|
"version": "0.1.29",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.19",
|
"version": "0.1.29",
|
||||||
"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",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-access/webui": {
|
"../../govoplan-access/webui": {
|
||||||
"name": "@govoplan/access-webui",
|
"name": "@govoplan/access-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-campaign/webui": {
|
"../../govoplan-campaign/webui": {
|
||||||
"name": "@govoplan/campaign-webui",
|
"name": "@govoplan/campaign-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.22",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"read-excel-file": "9.2.0"
|
"read-excel-file": "9.2.0"
|
||||||
},
|
},
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-datasources/webui": {
|
"../../govoplan-datasources/webui": {
|
||||||
"name": "@govoplan/datasources-webui",
|
"name": "@govoplan/datasources-webui",
|
||||||
"version": "0.1.18",
|
"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",
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-files/webui": {
|
"../../govoplan-files/webui": {
|
||||||
"name": "@govoplan/files-webui",
|
"name": "@govoplan/files-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",
|
||||||
@@ -436,7 +436,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-idm/webui": {
|
"../../govoplan-idm/webui": {
|
||||||
"name": "@govoplan/idm-webui",
|
"name": "@govoplan/idm-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 +547,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-policy/webui": {
|
"../../govoplan-policy/webui": {
|
||||||
"name": "@govoplan/policy-webui",
|
"name": "@govoplan/policy-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",
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.19",
|
"version": "0.1.27",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.19",
|
"version": "0.1.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.18",
|
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.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/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.18",
|
"@govoplan/campaign-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#v0.1.22",
|
||||||
"@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.18",
|
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.20",
|
||||||
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.18",
|
"@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/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/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",
|
||||||
@@ -753,8 +753,8 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@govoplan/access-webui": {
|
"node_modules/@govoplan/access-webui": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#44799b15e56cb1ace679689783be51aac7bee4c3",
|
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#2d1b1e356ecb8726219d4a502db78e61f435a88f",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
@@ -820,8 +820,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@govoplan/campaign-webui": {
|
"node_modules/@govoplan/campaign-webui": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.22",
|
||||||
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#696f8f638552417ceba48582303ca2523db933cb",
|
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git#4f52f010ee6a117b7b3ad605a59b1da0a459a0c6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"read-excel-file": "9.2.0"
|
"read-excel-file": "9.2.0"
|
||||||
},
|
},
|
||||||
@@ -874,8 +874,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@govoplan/files-webui": {
|
"node_modules/@govoplan/files-webui": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.20",
|
||||||
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#3a3360650f9f12238852de5ffc1ee35c24077ddd",
|
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#378f4d6ac5525d0f07f38eefce893bfa2924f2b9",
|
||||||
"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",
|
||||||
@@ -893,8 +893,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@govoplan/idm-webui": {
|
"node_modules/@govoplan/idm-webui": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#9bcd2de587fbce31e43221bfc4d4492597823dc5",
|
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#b0eda351957526bc7e59270c7a42016aa12bc1a3",
|
||||||
"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",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.19",
|
"version": "0.1.29",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.19",
|
"version": "0.1.27",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
"preview": "vite preview --host 127.0.0.1 --port 4173"
|
"preview": "vite preview --host 127.0.0.1 --port 4173"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.18",
|
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.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/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.18",
|
"@govoplan/files-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git#v0.1.20",
|
||||||
"@govoplan/idm-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git#v0.1.18",
|
"@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.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/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",
|
||||||
|
|||||||
Reference in New Issue
Block a user