Files
govoplan-workflow-engine/src/govoplan_workflow_engine/backend/manifest.py
T

465 lines
17 KiB
Python

from __future__ import annotations
from pathlib import Path
from govoplan_core.core.access import (
CAPABILITY_ACCESS_DIRECTORY,
CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
)
from govoplan_core.core.dataflows import CAPABILITY_DATAFLOW_RUN_LIFECYCLE
from govoplan_core.core.module_guards import (
drop_table_retirement_provider,
persistent_table_uninstall_guard,
)
from govoplan_core.core.modules import (
CapabilityDocumentation,
DocumentationTopic,
MigrationSpec,
ModuleContext,
ModuleInterfaceProvider,
ModuleInterfaceRequirement,
ModuleManifest,
PermissionDefinition,
RoleTemplate,
)
from govoplan_core.core.provider_governance import declared_module_architecture
from govoplan_core.core.policy import (
CAPABILITY_POLICY_DEFINITION_GOVERNANCE,
)
from govoplan_core.core.notifications import (
CAPABILITY_NOTIFICATIONS_DISPATCH,
)
from govoplan_core.core.references import CAPABILITY_ACCESS_REFERENCE_OPTIONS
from govoplan_core.core.views import CAPABILITY_VIEWS_RESOLVER
from govoplan_core.core.workflows import (
CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS,
CAPABILITY_WORKFLOW_ORCHESTRATION,
CAPABILITY_WORKFLOW_RUNTIME_WORKER,
CAPABILITY_WORKFLOW_TRIGGER_DISPATCHER,
)
from govoplan_core.db.base import Base
from govoplan_workflow_engine.backend.db import models as workflow_models
from govoplan_workflow_engine.backend.configuration_provider import (
WORKFLOW_CONFIGURATION_CAPABILITY,
)
from govoplan_workflow_engine.backend.service_launcher import (
CAPABILITY_WORKFLOW_SERVICE_LAUNCHER,
WorkflowServiceLauncher,
)
MODULE_ID = "workflow_engine"
MODULE_NAME = "Workflow Engine"
MODULE_VERSION = "0.1.14"
DEFINITION_READ_SCOPE = "workflow:definition:read"
DEFINITION_WRITE_SCOPE = "workflow:definition:write"
INSTANCE_READ_SCOPE = "workflow:instance:read"
INSTANCE_START_SCOPE = "workflow:instance:start"
INSTANCE_TRANSITION_SCOPE = "workflow:instance:transition"
ADMIN_SCOPE = "workflow:instance:admin"
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
module_id, resource, action = scope.split(":", 2)
return PermissionDefinition(
scope=scope,
label=label,
description=description,
category="Workflow",
level="tenant",
module_id=module_id,
resource=resource,
action=action,
)
PERMISSIONS = (
_permission(
DEFINITION_READ_SCOPE,
"View workflow definitions",
"Read workflow graphs, versions, diagnostics, and referenced contracts.",
),
_permission(
DEFINITION_WRITE_SCOPE,
"Manage workflow definitions",
"Create, edit, validate, and publish workflow definitions.",
),
_permission(
INSTANCE_READ_SCOPE,
"View workflow instances",
"Read workflow progress, pending actions, and transition evidence.",
),
_permission(
INSTANCE_START_SCOPE,
"Start workflows",
"Start approved workflow definitions for authorized subjects.",
),
_permission(
INSTANCE_TRANSITION_SCOPE,
"Advance workflows",
"Complete activities and invoke authorized workflow transitions.",
),
_permission(
ADMIN_SCOPE,
"Administer workflows",
"Manage workflow definitions and instances across the tenant.",
),
)
ROLE_TEMPLATES = (
RoleTemplate(
slug="workflow_designer",
name="Workflow designer",
description="Design, validate, and publish workflow definitions.",
permissions=(
DEFINITION_READ_SCOPE,
DEFINITION_WRITE_SCOPE,
INSTANCE_READ_SCOPE,
),
),
RoleTemplate(
slug="workflow_operator",
name="Workflow operator",
description="Start and advance approved workflows.",
permissions=(
DEFINITION_READ_SCOPE,
INSTANCE_READ_SCOPE,
INSTANCE_START_SCOPE,
INSTANCE_TRANSITION_SCOPE,
),
),
)
def _router(context: ModuleContext):
from govoplan_workflow_engine.backend.runtime import configure_runtime
configure_runtime(registry=context.registry, settings=context.settings)
from govoplan_workflow_engine.backend.router import router
return router
def _runtime_worker(context: ModuleContext):
from govoplan_workflow_engine.backend.instance_service import (
SqlWorkflowRuntimeWorker,
)
return SqlWorkflowRuntimeWorker(registry=context.registry)
def _trigger_dispatcher(context: ModuleContext):
from govoplan_workflow_engine.backend.triggers import (
SqlWorkflowTriggerDispatcher,
)
return SqlWorkflowTriggerDispatcher(registry=context.registry)
def _definition_contribution_provider(context: ModuleContext):
from govoplan_workflow_engine.backend.contributions import (
SqlWorkflowDefinitionContributionProvider,
)
return SqlWorkflowDefinitionContributionProvider(registry=context.registry)
def _configuration_provider(context: ModuleContext):
from govoplan_workflow_engine.backend.configuration_provider import (
SqlWorkflowConfigurationProvider,
)
return SqlWorkflowConfigurationProvider(registry=context.registry)
def _orchestration_provider(context: ModuleContext):
from govoplan_workflow_engine.backend.orchestration import (
SqlWorkflowOrchestrationProvider,
)
return SqlWorkflowOrchestrationProvider(registry=context.registry)
def _service_launcher(context: ModuleContext) -> WorkflowServiceLauncher:
return WorkflowServiceLauncher(registry=context.registry)
manifest = ModuleManifest(
id=MODULE_ID,
name=MODULE_NAME,
version=MODULE_VERSION,
permission_namespace="workflow",
dependencies=(),
optional_dependencies=(
"access",
"audit",
"dataflow",
"datasources",
"notifications",
"policy",
"tasks",
"views",
),
optional_capabilities=(
CAPABILITY_ACCESS_DIRECTORY,
CAPABILITY_ACCESS_REFERENCE_OPTIONS,
CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER,
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
CAPABILITY_DATAFLOW_RUN_LIFECYCLE,
CAPABILITY_NOTIFICATIONS_DISPATCH,
CAPABILITY_POLICY_DEFINITION_GOVERNANCE,
CAPABILITY_VIEWS_RESOLVER,
),
provides_interfaces=(
ModuleInterfaceProvider(name="workflow.definition_graph", version="0.1.0"),
ModuleInterfaceProvider(name="workflow.node_library", version="0.1.0"),
ModuleInterfaceProvider(name="workflow.definition_catalogue", version="0.1.0"),
ModuleInterfaceProvider(
name="workflow.definition_contributions",
version="0.1.0",
),
ModuleInterfaceProvider(
name=WORKFLOW_CONFIGURATION_CAPABILITY,
version="1.0.0",
),
ModuleInterfaceProvider(
name=CAPABILITY_WORKFLOW_ORCHESTRATION,
version="1.0.0",
),
ModuleInterfaceProvider(name="workflow.runtime_worker", version=MODULE_VERSION),
ModuleInterfaceProvider(
name="workflow.trigger_dispatcher",
version="1.0.0",
),
ModuleInterfaceProvider(name="workflow.bpmn_interchange", version="1.0.0"),
ModuleInterfaceProvider(
name=CAPABILITY_WORKFLOW_SERVICE_LAUNCHER,
version="0.1.0",
),
ModuleInterfaceProvider(
name="workflow.bpmn_execution_adapters",
version="1.0.0",
),
),
requires_interfaces=(
ModuleInterfaceRequirement(
name=CAPABILITY_ACCESS_REFERENCE_OPTIONS,
version_min="0.1.0",
version_max_exclusive="0.2.0",
optional=True,
),
ModuleInterfaceRequirement(
name="dataflow.run_lifecycle",
version_min="0.1.14",
version_max_exclusive="1.0.0",
optional=True,
),
ModuleInterfaceRequirement(
name="auth.automation_principal",
version_min="0.1.0",
version_max_exclusive="1.0.0",
optional=True,
),
ModuleInterfaceRequirement(
name=CAPABILITY_NOTIFICATIONS_DISPATCH,
version_min="0.1.0",
version_max_exclusive="1.0.0",
optional=True,
),
ModuleInterfaceRequirement(
name="policy.definition_governance",
version_min="0.1.0",
version_max_exclusive="1.0.0",
optional=True,
),
ModuleInterfaceRequirement(
name="views.resolver",
version_min="0.1.0",
version_max_exclusive="1.0.0",
optional=True,
),
),
permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES,
route_factory=_router,
capability_factories={
CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS: (
_definition_contribution_provider
),
CAPABILITY_WORKFLOW_RUNTIME_WORKER: _runtime_worker,
CAPABILITY_WORKFLOW_TRIGGER_DISPATCHER: _trigger_dispatcher,
CAPABILITY_WORKFLOW_ORCHESTRATION: _orchestration_provider,
WORKFLOW_CONFIGURATION_CAPABILITY: _configuration_provider,
CAPABILITY_WORKFLOW_SERVICE_LAUNCHER: _service_launcher,
},
capability_documentation={
CAPABILITY_WORKFLOW_SERVICE_LAUNCHER: CapabilityDocumentation(
label="Workflow service launcher",
summary="Starts an authorized active Workflow from an exact available Service revision.",
contract_version="0.1.0",
),
},
migration_spec=MigrationSpec(
module_id=MODULE_ID,
metadata=Base.metadata,
script_location=str(Path(__file__).with_name("migrations") / "versions"),
retirement_supported=True,
retirement_provider=drop_table_retirement_provider(
workflow_models.WorkflowWaitState,
workflow_models.WorkflowTriggerDelivery,
workflow_models.WorkflowTrigger,
workflow_models.WorkflowInstanceEvent,
workflow_models.WorkflowInstanceStep,
workflow_models.WorkflowInstance,
workflow_models.WorkflowDefinitionRevision,
workflow_models.WorkflowDefinition,
label="Workflow",
),
retirement_notes=(
"Destructive retirement drops Workflow definitions and immutable revisions "
"after the installer captures a database snapshot."
),
),
uninstall_guard_providers=(
persistent_table_uninstall_guard(
workflow_models.WorkflowDefinition,
workflow_models.WorkflowDefinitionRevision,
workflow_models.WorkflowInstance,
workflow_models.WorkflowInstanceStep,
workflow_models.WorkflowInstanceEvent,
workflow_models.WorkflowTrigger,
workflow_models.WorkflowTriggerDelivery,
workflow_models.WorkflowWaitState,
label="Workflow",
),
),
documentation=(
DocumentationTopic(
id="workflow.definition-graphs",
title="Workflow definition graphs",
summary="Governed process graphs exposed independently of an editor.",
body=(
"Workflow Engine provides trigger, activity, decision, wait, integration, "
"and outcome node library on top of Core's domain-neutral graph contract. "
"Unlike Dataflow, Workflow permits cycles for correction and retry paths. "
"Module actions are addressed through versioned capabilities rather than "
"implementation imports. Definitions are persisted as immutable graph "
"revisions; activation pins the exact revision used by future instances. "
"The optional service-launch capability starts an authorized active "
"revision from an exact Portal Service binding and records that provenance."
),
layer="available",
documentation_types=("admin", "user"),
audience=("operator", "module_admin", "power_user", "product_owner"),
related_modules=(
"dataflow",
"datasources",
"tasks",
"notifications",
"audit",
),
order=76,
),
DocumentationTopic(
id="workflow.bpmn-interchange",
title="BPMN modeling and execution profiles",
summary=(
"Lossless BPMN 2.0 revisions with explicit, fail-closed "
"execution conformance."
),
body=(
"BPMN 2.0 is Workflow's canonical native graph language. The "
"shared graph editor models BPMN nodes, flows, containment, and "
"diagram geometry directly without a separate browser-side "
"modeler. Imported XML is normalized into the graph and every "
"immutable revision pins a deterministic XML artifact. Activation "
"requires a pinned execution adapter and version whose declared "
"profile accepts every modeled runtime semantic. Unsupported "
"runtime constructs remain editable and exportable. "
"Adapter packages integrate through the "
"govoplan.workflow.bpmn_adapters entry-point group and must "
"materialize canonical Workflow runtime state; Workflow Engine never "
"imports a concrete engine module."
),
layer="available",
documentation_types=("admin", "user"),
audience=("operator", "module_admin", "power_user", "product_owner"),
related_modules=("audit", "policy", "views"),
order=77,
),
DocumentationTopic(
id="workflow.runtime-recovery",
title="Workflow runtime recovery",
summary=(
"Fenced module actions, timers, and evidence-based recovery "
"for unknown provider outcomes."
),
body=(
"Workflow Engine records a Core recovery operation before every "
"consequential module-action dispatch and commits a conclusive "
"provider result with the local Workflow projection. A timeout or "
"lost acknowledgement after non-atomic dispatch becomes an unknown "
"outcome and disables Retry. Operators inspect the provider, record "
"evidence, and choose Effect confirmed to continue without replay or "
"Effect absent to enable a deliberate retry. Instance workers, "
"trigger deliveries, and timers use distributed fences and are partitioned by tenant module entitlement before state is claimed. Disabling Workflow Engine preserves accepted instances, waits, and trigger deliveries for operator resolution. Linked "
"Dataflow recovery remains blocked until its result is conclusive."
),
layer="available",
documentation_types=("admin", "user"),
audience=("operator", "module_admin", "power_user"),
related_modules=("core", "dataflow", "audit"),
order=78,
),
),
architecture=declared_module_architecture(
layer="human_work_procedure",
kind="runtime",
maturity="vertical_slice",
documentation_ref="docs/ENGINE_EDITOR_SPLIT.md",
test_ref="tests/test_instance_service.py",
known_limits=(
"Execution adapters support declared conformance profiles but do not cover every editable BPMN semantic.",
"Native schedules intentionally support one-time and bounded interval starts; cron requires a future governed scheduler adapter.",
),
owned_concepts=(
"workflow definition",
"workflow revision",
"workflow instance",
"work transition",
"execution adapter binding",
),
non_owned_concepts=(
"visual editor",
"domain action",
"notification",
"dataflow run",
),
recovery_docs=("docs/CONCEPT.md", "docs/DURABLE_RUNTIME_RECOVERY.md"),
security_docs=("docs/CONCEPT.md",),
operations_docs=("README.md",),
),
)
def get_manifest() -> ModuleManifest:
return manifest
__all__ = [
"ADMIN_SCOPE",
"DEFINITION_READ_SCOPE",
"DEFINITION_WRITE_SCOPE",
"INSTANCE_READ_SCOPE",
"INSTANCE_START_SCOPE",
"INSTANCE_TRANSITION_SCOPE",
"MODULE_ID",
"MODULE_VERSION",
"get_manifest",
"manifest",
]